
TypeScript, a strongly typed superset of JavaScript, is essential for building scalable applications, making it a crucial skill for frontend developers, full-stack engineers, and software architects. Stark.ai offers a curated collection of TypeScript interview questions, real-world scenarios, and expert guidance to help you excel in your next technical interview.
TypeScript is a strongly typed, object-oriented programming language that builds on JavaScript. It is a superset of...
TypeScript includes several basic data types: number (for both integers and floating-point values), string (for...
In TypeScript, variables can be declared with type annotations using the colon (:) syntax. For example: 'let name:...
Type inference is TypeScript's ability to automatically determine types of variables based on their values and...
The 'any' type is a dynamic type that can hold values of any type. It effectively opts out of type checking for that...
Both interface and type are used to define custom types, but they have some differences. Interfaces are extensible...
TypeScript treats null and undefined as distinct types. By default, with strict null checks enabled...
Literal types are specific value types in TypeScript where you can specify the exact value a variable can have. For...
Type assertions are a way to tell the TypeScript compiler that you know better about the type of a value. They can...
The 'never' type represents values that never occur. It is used for functions that never return (throw an error or...
Arrays in TypeScript can be typed in two ways: using the type followed by [] (e.g., 'number[]') or using the generic...
Union types allow a variable to hold values of multiple types. They are created using the | operator. For example:...
Type narrowing is the process of refining types to more specific ones based on type guards and conditional checks....
Type guards are expressions that perform runtime checks to guarantee the type of a value in a scope. They can be...
Enums allow defining a set of named numeric constants. There are numeric enums (default values auto-increment from...
Mapped types allow you to create new types based on existing ones by transforming each property in the same way....
Conditional types select one of two possible types based on a condition expressed as a type relationship test. They...
The keyof operator takes an object type and produces a string or numeric literal union of its keys. It's useful for...
Intersection types combine multiple types into one using the & operator. The resulting type has all properties from...
TypeScript's typeof operator can be used in type contexts to reference the type of a variable or property. Unlike...
Utility types are built-in types that facilitate common type transformations. Common utility types include:...
The infer keyword is used in conditional types to extract and infer type parameters from other types. It's commonly...
Type widening is when TypeScript expands a narrow type to a more general one (like expanding a literal type to its...
Type aliases are created using the type keyword followed by a name and type definition. They can represent simple...
Index types allow you to create objects with dynamic property names while maintaining type safety. Index signatures...
The unknown type is a type-safe alternative to any. While any allows all operations without type checking, unknown...
Template literal types combine literal types through template literal strings. They allow creating new string...
Discriminated unions are union types where each member has a common property (the discriminant) that TypeScript can...
Omit<T, K> constructs a type by picking all properties from T and then removing those in K. It's useful for creating...
Type assertions tell TypeScript to treat a value as a specific type using 'as' or angle bracket syntax. Type...
Pick<T, K> constructs a type by picking a set of properties K from type T. It's useful when you want to create a new...
Recursive types are types that reference themselves in their definition. They're useful for representing tree-like...
ReturnType<T> extracts the return type of a function type T. It's useful when you want to reference the return type...
Readonly<T> creates a new type where all properties of T are readonly. It's useful when you want to ensure that...
Lookup types allow you to extract the type of a property from another type using indexed access notation. Example:...
Literal type widening occurs when TypeScript widens a literal type to its base type. Const assertions (using 'as...
Extract<T, U> extracts from T all types that are assignable to U. It's useful for filtering union types. Example:...
Record<K,T> creates an object type with properties of type K and values of type T, while index signatures use [key:...
TypeScript classes are blueprints for creating objects that extend JavaScript classes with additional features like...
TypeScript supports three access modifiers: public (default, accessible everywhere), private (only within the...
Abstract classes are base classes that cannot be instantiated directly and may contain abstract methods that must be...
TypeScript supports single inheritance using the 'extends' keyword. A class can inherit properties and methods from...
Parameter properties are a TypeScript shorthand that allows you to both declare and initialize class members in the...
Classes can implement interfaces using the 'implements' keyword. The class must provide implementations for all...
Static members (properties and methods) belong to the class itself rather than instances of the class. They're...
TypeScript supports JavaScript's get and set accessors with additional type safety. They allow you to add logic when...
Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its...
Polymorphism in TypeScript can be achieved through method overriding and interfaces. It allows objects of different...
Private constructors prevent class instantiation from outside the class. They're commonly used in singleton pattern...
Singleton pattern ensures a class has only one instance. In TypeScript, it's implemented using a private constructor...
The 'readonly' modifier makes class properties immutable after initialization. They must be initialized at...
TypeScript supports method overloading through function declarations with different parameter types or numbers....
Abstract methods are methods declared in abstract classes without implementation. They must be implemented by...
The Factory pattern provides an interface for creating objects without specifying exact classes. In TypeScript, it's...
Protected constructors allow instantiation only from within the class itself or its derived classes. This pattern is...
Mixins are a way to combine multiple classes into one. TypeScript implements mixins using class expressions and a...
Index signatures in classes allow you to define dynamic properties with a specific type. They're defined using [key:...
The Observer pattern is implemented using interfaces for Subject and Observer, with the Subject maintaining a list...
Composition involves creating complex objects by combining simpler ones, while inheritance creates relationships...
The 'super' keyword is used to call methods or access properties on the parent class. It's commonly used in...
Type guards in class hierarchies help narrow down types when working with inheritance. The instanceof operator is...
TypeScript offers several ways to define function types: 1) Using interface: interface Func { (x: number): number;...
Optional parameters are marked with a '?' after the parameter name. They must come after required parameters....
Rest parameters allow functions to accept an indefinite number of arguments as an array. They're denoted by '...'...
Function overloading involves declaring multiple function signatures followed by a single implementation that...
Generic functions allow creating reusable functions that can work with multiple types while maintaining type safety....
Default parameters provide fallback values for parameters that are not passed to the function. Example: function...
Arrow functions provide a concise syntax for function expressions and lexically bind 'this'. Example: let add = (a:...
TypeScript allows typing 'this' parameter as the first parameter in function declarations. Example: function...
Higher-order functions are functions that take other functions as parameters or return functions. Example: function...
Callbacks can be typed using function types or interfaces. Example: interface Callback { (error: Error | null,...
Function type assertions allow you to tell TypeScript that a function is of a specific type. Example: const myFunc =...
Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument....
Function type intersections combine multiple function types into one that must satisfy all types. Example: type...
Async functions are typed with Promise<T> where T is the type of the resolved value. Example: async function...
Generator functions return iterables using the function* syntax and yield keyword. Example: function* range(start:...
Method chaining involves returning this from methods to enable consecutive calls. Example: class Calculator {...
Function type unions allow a function to have multiple possible types. Example: type StringOrNumberFunc = ((x:...
Function decorators are typed as functions that take a function descriptor and return a new descriptor or value....
TypeScript can infer function return types and sometimes parameter types based on usage and context. Example:...
Function composition combines multiple functions into a single function, applying them in sequence. Example: const...
Contextual typing allows TypeScript to infer types based on the context where a function is used. Example: const...
Event handlers are typically typed using the appropriate event interface. Example: function handleClick(event:...
Call signatures define how a function can be called, while construct signatures define how a function can be used...
Function properties are typed using regular property syntax on function types. Example: interface FunctionWithProps...
Function type constraints limit what types can be used with generic functions. Example: function longest<T extends {...
Generics are a way to create reusable components that can work with multiple types while maintaining type safety....
Generic constraints limit what types can be used with a generic type using the 'extends' keyword. Example: function...
Generic interfaces are interfaces that can work with multiple types. Example: interface Container<T> { value: T;...
Conditional types select a type based on a condition, using syntax similar to ternary operators: T extends U ? X :...
TypeScript can infer generic types from usage context. Example: function identity<T>(arg: T): T { return arg; } let...
Type parameters are placeholders for types in generic definitions, conventionally denoted by T, U, K, V, etc....
Generic classes use type parameters to create flexible, reusable class definitions. Example: class Stack<T> {...
Mapped types create new types by transforming properties of existing types. Example: type Readonly<T> = { readonly...
The infer keyword extracts type information within conditional types. Example: type ReturnType<T> = T extends...
Generic type defaults provide fallback types when no type argument is specified. Example: interface Container<T =...
Multiple type parameters allow generics to work with multiple types simultaneously. Example: function pair<T,...
Index types allow type-safe access to properties using dynamic keys. Example: function getProperty<T, K extends...
Generic type guards combine generics with type predicates. Example: function isOfType<T>(value: any, property: keyof...
Union and intersection types can be used with generics to create flexible type combinations. Example: function...
Generic type aliases create reusable type definitions with type parameters. Example: type Result<T> = { success:...
Generic constraints can use union and intersection types to create complex type requirements. Example: function...
Branded types are nominal types created using intersection with unique symbols. Example: type Brand<T, B> = T & {...
The keyof operator can be used with generics to create type-safe property access. Example: function...
Variadic tuple types allow working with tuples of variable length in a type-safe way. Example: type Concat<T extends...
Template literal types can be combined with generics to create dynamic string types. Example: type PropEventType<T...
Generic type assertions allow specifying type parameters when using type assertions. Example: function...
Generic default types can be combined with constraints to provide flexible yet safe defaults. Example: interface...
Distributive conditional types apply conditions to each member of a union type. Example: type ToArray<T> = T extends...
Generic event emitters provide type safety for event handling. Example: class EventEmitter<Events extends...
Key differences include: 1) Interfaces are extendable through declaration merging while type aliases are not, 2)...
Interfaces can inherit from other interfaces using the 'extends' keyword. They can extend multiple interfaces,...
Optional properties in interfaces are marked with a '?' symbol after the property name. They don't need to be...
Readonly properties are marked with the 'readonly' modifier and cannot be changed after initialization. Example:...
Index signatures allow interfaces to describe objects with dynamic property names. Example: interface StringMap {...
Function types in interfaces can be defined using call signatures. Example: interface Calculation { (x: number, y:...
Declaration merging allows multiple interface declarations with the same name to be automatically combined. Example:...
Interfaces can use generic type parameters to create flexible, reusable definitions. Example: interface Container<T>...
Hybrid types combine function types with object properties. Example: interface Counter { (start: number): string;...
An interface can extend multiple interfaces using comma-separated names. Example: interface Shape { color: string; }...
Recursive types are interfaces that reference themselves in their definition. Example: interface TreeNode { value:...
Mapped types can be used to transform interface properties systematically. Example: type Readonly<T> = { readonly [P...
Utility types provide common type transformations for interfaces. Example: interface User { name: string; age?:...
Method overloading in interfaces is achieved by declaring multiple function signatures. Example: interface Document...
Union types (|) allow a value to be one of several types, while intersection types (&) combine multiple types....
Type guards help narrow down interface types in conditional blocks. Example: interface Bird { fly(): void; }...
Computed properties allow property names to be expressions. Example: type Events = 'click' | 'focus'; interface...
Classes can implement interfaces using the 'implements' keyword. Example: interface Printable { print(): void; }...
Literal types specify exact values that a property must have. Example: interface Config { mode: 'development' |...
Interfaces can be used as constraints for generic types. Example: interface Lengthwise { length: number; } function...
Optional methods in interfaces are marked with '?' and don't need to be implemented. Example: interface Logger {...
Interfaces can describe array-like structures using index signatures or extending Array. Example: interface...
Constructor signatures define how a class constructor should look. Example: interface ClockConstructor { new (hour:...
Interfaces can define multiple call signatures for function overloading. Example: interface StringNumberFunction {...
Interfaces can define static members that must be implemented by the class itself rather than instances. Example:...
ES modules are the standard JavaScript module system that TypeScript supports. They use import/export syntax and...
TypeScript supports various export/import syntaxes: 1) Named exports: export { MyClass }; 2) Default exports: export...
Namespaces (formerly 'internal modules') are TypeScript's own module system for encapsulating code. They use the...
Barrel exports consolidate multiple exports into a single entry point using an index.ts file. Example: export * from...
Module augmentation allows you to add new declarations to existing modules. Example: declare module 'lodash' {...
Circular dependencies occur when two modules import each other. They can be resolved by: 1) Restructuring code to...
Ambient modules declare types for JavaScript modules that don't have TypeScript declarations. They use declare...
Namespace merging allows combining multiple namespace declarations with the same name. Example: namespace Animals {...
TypeScript supports several module resolution strategies: 1) Classic: Legacy resolution for AMD/System.js, 2) Node:...
Dynamic imports allow loading modules on demand using import() syntax. Example: async function loadModule() { const...
Declaration files (.d.ts) contain type information for JavaScript modules. They define the shape of modules without...
Re-exports allow you to export items from other modules. Syntax includes: export { Something } from './other',...
Best practices include: 1) One class/feature per file, 2) Use barrel exports (index.ts) for related features, 3)...
Module path aliases are configured in tsconfig.json using the paths option. Example: { 'compilerOptions': {...
Internal modules (namespaces) use the namespace keyword and are TypeScript-specific. External modules (ES modules)...
Namespaces use export keyword for public members and can be accessed using dot notation. Example: namespace Math {...
Synthetic default imports allow importing modules that don't have explicit default exports as if they did....
Module side effects are handled using import statements without bindings. Example: import './polyfills'; This...
Global modules are ambient modules that add types to the global scope. Example: declare global { interface Window {...
Type-only imports/exports are used for importing/exporting types without value emissions. Syntax: import type {...
Lazy loading patterns include: 1) Dynamic imports: import('./module'), 2) Route-based splitting: loadChildren in...
Monorepo module resolution involves: 1) Using project references in tsconfig.json, 2) Configuring path aliases for...
Module augmentation can be done through: 1) Declaration merging: declare module 'module' {}, 2) Global augmentation:...
Module resolution fallbacks are configured through: 1) moduleResolution in tsconfig.json, 2) baseUrl and paths for...
Decorators are special declarations that can be attached to class declarations, methods, properties, or parameters....
TypeScript supports five types of decorators: 1) Class decorators (@classDecorator), 2) Method decorators...
Class decorators are declared before a class declaration and receive the constructor as an argument. Example:...
Method decorators are declared before method declarations and receive three arguments: target (prototype),...
reflect-metadata is a library that adds a polyfill for the Metadata Reflection API. It's used with decorators to add...
Property decorators receive two arguments: target (prototype) and propertyKey (property name), unlike method...
Decorator factories are functions that return decorators and allow customization through parameters. Example:...
Parameter decorators are applied to method parameters and receive three arguments: target (prototype), methodName,...
Accessor decorators are applied to getter/setter declarations and receive three arguments similar to method...
Multiple decorators can be applied to a declaration in sequence. They are evaluated in reverse order (bottom to...
Metadata allows storing additional information about classes, methods, and properties that can be accessed at...
Validation decorators can be implemented using property or parameter decorators with metadata. Example: function...
Common use cases include: 1) Logging and monitoring, 2) Property validation, 3) Dependency injection, 4) Method...
Decorators can implement dependency injection by using metadata to store and retrieve dependencies. Example:...
Limitations include: 1) Experimental feature requiring compiler flag, 2) Can't decorate declarations in .d.ts files,...
Memoization decorators cache function results based on arguments. Example: function memoize(target: any, key:...
Design-time type metadata is automatically generated when emitDecoratorMetadata is enabled. It includes type...
Method override decorators can verify proper method overriding. Example: function override(target: any, key: string,...
Best practices include: 1) Use decorator factories for configuration, 2) Keep decorators focused and single-purpose,...
Lazy initialization decorators delay property initialization until first access. Example: function...
Async operations in decorators require special handling since decorators run during class definition. Example:...
Key differences include: 1) Syntax variations, 2) TypeScript decorators are experimental while ES decorators are...
Custom error types can be created by extending the Error class. Example: class ValidationError extends Error {...
Union types in error handling allow functions to return either a success value or an error type. Example: type...
Async errors can be handled using try-catch with async/await or .catch() with Promises. Example: async function...
Type guards help narrow down error types for proper handling. Example: function isNetworkError(error: unknown):...
The 'unknown' type is safer than 'any' for error handling as it requires type checking before use. Example: function...
TypeScript debugging tools include: 1) Source maps for debugging compiled code, 2) VS Code's built-in debugger, 3)...
Error boundaries are implemented using class components with error handling lifecycle methods. Example: class...
Source maps enable debugging of TypeScript code directly, even though the browser runs compiled JavaScript. They map...
Type assertions should be used carefully in error handling to maintain type safety. Example: function...
Best practices include: 1) Use custom error classes for specific error types, 2) Implement type-safe error handling...
Error logging can be implemented using a centralized error logging service. Example: class ErrorLogger { static...
throw is used for synchronous error handling and immediately stops execution, while reject is used in Promises for...
TypeScript tests can be debugged using: 1) Jest's debugger with ts-jest, 2) VS Code's debug configuration for tests,...
Retry logic can be implemented using recursive functions or libraries with proper error handling. Example: async...
Conditional types help create type-safe error handling patterns. Example: type ErrorResponse<T> = T extends Error ?...
Promise rejections can be handled using .catch(), try-catch with async/await, or global handlers. Example:...
Discriminated unions provide type-safe error handling by using a common property to discriminate between success and...
Memory leaks can be debugged using: 1) Chrome DevTools Memory panel, 2) Heap snapshots, 3) Memory allocation...
Decorators can implement error handling through method wrapping. Example: function catchErrors() { return...
Circuit breakers prevent cascading failures by stopping operations after too many errors. Example: class...
tsconfig.json is the main configuration file for TypeScript projects. Key components include: 1) compilerOptions for...
Key compiler options include: 1) target (specifies ECMAScript target version), 2) module (module code generation),...
Module resolution is configured through tsconfig.json options: 1) moduleResolution ('node' or 'classic'), 2) baseUrl...
TypeScript integration with webpack requires: 1) ts-loader or babel-loader with @babel/preset-typescript, 2) source...
Project references allow splitting TypeScript projects into smaller pieces for better organization and build...
Different environments can be configured using: 1) Multiple tsconfig files (tsconfig.dev.json, tsconfig.prod.json),...
Declaration files (.d.ts) provide type information for JavaScript code. They can be generated using the declaration...
Strict type checking is configured using the strict flag and individual flags: 1) strictNullChecks, 2)...
TypeScript tooling options include: 1) VS Code with built-in TypeScript support, 2) WebStorm with TypeScript...
Path aliases are configured using baseUrl and paths in tsconfig.json. Example: { 'compilerOptions': { 'baseUrl':...
Best practices include: 1) Using project references for large codebases, 2) Consistent file/folder structure, 3)...
Monorepo configuration involves: 1) Using project references, 2) Setting up shared configurations, 3) Configuring...
Language Service plugins extend TypeScript's language service functionality. They can add custom type checking, code...
Incremental compilation is configured using: 1) incremental flag, 2) tsBuildInfoFile option, 3) composite project...
TypeScript supports multiple module systems: 1) ES Modules (import/export), 2) CommonJS (require/exports), 3) AMD,...
Source maps are configured using: 1) sourceMap compiler option, 2) sourceRoot option, 3) mapRoot option. Example: {...
Assets can be handled through: 1) Declaration files for non-code assets, 2) Module declarations for imports, 3)...
Testing configuration includes: 1) Separate tsconfig.test.json, 2) Jest configuration with ts-jest, 3) Test-specific...
TypeScript supports different build modes: 1) Regular compilation (tsc), 2) Watch mode (tsc -w), 3) Project...
Configuration varies by bundler: 1) Webpack: ts-loader or babel-loader, 2) Rollup: @rollup/plugin-typescript, 3)...
TypeScript is a strongly typed, object-oriented programming language that builds on JavaScript. It is a superset of JavaScript that adds optional static typing, classes, interfaces, and modules. The key differences include: static typing, enhanced IDE support, object-oriented features, and compilation to JavaScript. TypeScript code needs to be compiled into JavaScript before it can be run in a browser or Node.js environment.
TypeScript includes several basic data types: number (for both integers and floating-point values), string (for text), boolean (true/false), null, undefined, void (absence of value), any (dynamic typing), never (never occurs), and symbol (unique identifiers). It also supports arrays, tuples, and custom types through interfaces and type aliases.
In TypeScript, variables can be declared with type annotations using the colon (:) syntax. For example: 'let name: string = "John"', 'let age: number = 25', 'let isStudent: boolean = true'. Type annotations are optional due to TypeScript's type inference, but they make the code more explicit and self-documenting.
Type inference is TypeScript's ability to automatically determine types of variables based on their values and usage. When you declare a variable with an initial value, TypeScript will use that value to infer its type. For example, 'let name = "John"' will be inferred as string type. This reduces the need for explicit type annotations while maintaining type safety.
The 'any' type is a dynamic type that can hold values of any type. It effectively opts out of type checking for that variable. While it provides flexibility, it should be used sparingly as it defeats the purpose of TypeScript's type system. Common use cases include: working with dynamic data, gradual migration from JavaScript, and when dealing with third-party libraries without type definitions.
Both interface and type are used to define custom types, but they have some differences. Interfaces are extensible through declaration merging (can be added to later), while types are not. Interfaces can only describe object shapes, while types can also create unions, intersections, and other complex types. Generally, interfaces are preferred when defining object shapes, while types are better for aliases and complex type manipulations.
TypeScript treats null and undefined as distinct types. By default, with strict null checks enabled (strictNullChecks compiler option), you cannot assign null or undefined to a variable unless its type explicitly allows it. You can use union types to allow null or undefined values, such as 'let name: string | null = null'. This helps prevent null reference errors common in JavaScript.
Literal types are specific value types in TypeScript where you can specify the exact value a variable can have. For example: 'let direction: "north" | "south" | "east" | "west"'. This creates a type that can only have those specific string values. Literal types can be strings, numbers, or boolean values, and are often used in union types to create enumerated sets of allowed values.
Type assertions are a way to tell the TypeScript compiler that you know better about the type of a value. They can be written in two ways: using angle brackets '<type>' or the 'as' keyword. For example: 'let someValue: any = "hello"; let strLength: number = (<string>someValue).length;' or 'let strLength: number = (someValue as string).length;'. They should be used sparingly and only when you have more information about a type than TypeScript can determine.
The 'never' type represents values that never occur. It is used for functions that never return (throw an error or have infinite loops), and in type operations that result in no possible values. It's a bottom type, meaning it's assignable to every type, but no type is assignable to never (except never itself). Common use cases include error handling functions and exhaustive type checks.
Arrays in TypeScript can be typed in two ways: using the type followed by [] (e.g., 'number[]') or using the generic Array<type> syntax (e.g., 'Array<number>'). You can also create arrays of multiple types using union types. TypeScript provides type checking for array operations and methods. Example: 'let numbers: number[] = [1, 2, 3]' or 'let numbers: Array<number> = [1, 2, 3]'.
Union types allow a variable to hold values of multiple types. They are created using the | operator. For example: 'let id: string | number' means id can be either a string or a number. Union types are useful when a value could be one of several types, and TypeScript will ensure type safety by only allowing operations that are valid for all possible types in the union.
Type narrowing is the process of refining types to more specific ones based on type guards and conditional checks. Common type narrowing techniques include typeof checks, instanceof operators, in operator, and custom type predicates. For example, if you have a union type 'string | number' and check typeof x === 'string', TypeScript will narrow the type to string within that conditional block.
Type guards are expressions that perform runtime checks to guarantee the type of a value in a scope. They can be built-in (typeof, instanceof), custom functions (user-defined type predicates using 'is'), or the 'in' operator. Type guards help TypeScript narrow down types in conditional blocks, making the code more type-safe. Example: 'function isString(value: any): value is string { return typeof value === "string"; }'
Enums allow defining a set of named numeric constants. There are numeric enums (default values auto-increment from 0) and string enums (must set all values explicitly). Example: 'enum Direction { North, South, East, West }' or 'enum Direction { North = "N", South = "S" }'. Enums can be used as types and values, and TypeScript provides type safety when working with enum values.
Mapped types allow you to create new types based on existing ones by transforming each property in the same way. They use the syntax { [P in keyof T]: NewType }. Common examples include making all properties optional (Partial<T>), readonly (Readonly<T>), or nullable. This is powerful for type transformations and creating utility types.
Conditional types select one of two possible types based on a condition expressed as a type relationship test. They use the syntax T extends U ? X : Y. They're powerful for creating complex type logic and are often used with generics. For example: type NonNullable<T> = T extends null | undefined ? never : T.
The keyof operator takes an object type and produces a string or numeric literal union of its keys. It's useful for creating types that depend on the properties of other types. For example: type Point = { x: number; y: number }; type PointKey = keyof Point; // type PointKey = 'x' | 'y'
Intersection types combine multiple types into one using the & operator. The resulting type has all properties from all the constituent types. For example: type Employee = Person & { employeeId: number }. This creates a new type that must satisfy all the requirements of both Person and the object with employeeId.
TypeScript's typeof operator can be used in type contexts to reference the type of a variable or property. Unlike JavaScript's typeof which returns a string at runtime, TypeScript's typeof is used in type positions to create type references. Example: const point = { x: 0, y: 0 }; type Point = typeof point; // creates a type with shape { x: number, y: number }
Utility types are built-in types that facilitate common type transformations. Common utility types include: Partial<T> (makes all properties optional), Required<T> (makes all properties required), Readonly<T> (makes all properties readonly), Pick<T,K> (constructs type with subset of properties), Record<K,T> (constructs type with properties of keys K and type T), Exclude<T,U>, and Extract<T,U>.
The infer keyword is used in conditional types to extract and infer type parameters from other types. It's commonly used for complex type manipulations and for extracting types from functions, promises, and arrays. Example: type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any; This extracts the return type of a function type.
Type widening is when TypeScript expands a narrow type to a more general one (like expanding a literal type to its base type). Type narrowing is the opposite - restricting a type to a more specific one through control flow analysis, type guards, or assertions. Understanding these concepts is crucial for working with TypeScript's type system effectively.
Type aliases are created using the type keyword followed by a name and type definition. They can represent simple types, unions, intersections, tuples, or complex object types. Example: type Point = { x: number; y: number }; type ID = string | number; They provide a way to give meaningful names to types and reduce repetition.
Index types allow you to create objects with dynamic property names while maintaining type safety. Index signatures use the syntax [key: string]: ValueType to specify that an object can have any number of properties with names of type string (or number) and values of ValueType. Example: type Dictionary = { [key: string]: string }; This allows any string keys with string values.
The unknown type is a type-safe alternative to any. While any allows all operations without type checking, unknown requires type checking or assertion before operations can be performed. This makes unknown safer than any because it forces you to perform type checks before using the value. It's ideal for representing values whose type you don't know at compile time.
Template literal types combine literal types through template literal strings. They allow creating new string literal types by concatenating existing ones. Example: type EmailLocale = 'en' | 'fr' | 'de'; type EmailType = 'welcome' | 'goodbye'; type EmailTemplate = `${EmailLocale}_${EmailType}`; // Creates types like 'en_welcome', 'fr_goodbye', etc.
Discriminated unions are union types where each member has a common property (the discriminant) that TypeScript can use to narrow down the specific type. Example: type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; sideLength: number }. The 'kind' property helps TypeScript determine which shape type is being used.
Omit<T, K> constructs a type by picking all properties from T and then removing those in K. It's useful for creating new types that exclude certain properties. Example: type Person = { name: string; age: number; email: string }; type PersonWithoutEmail = Omit<Person, 'email'>; // Results in { name: string; age: number }
Type assertions tell TypeScript to treat a value as a specific type using 'as' or angle bracket syntax. Type declarations explicitly define the type using ':'. Assertions are used when you know more about a type than TypeScript can infer, while declarations are used to specify what type a variable should have. Example: let str1 = value as string; (assertion) vs let str2: string = value; (declaration)
Pick<T, K> constructs a type by picking a set of properties K from type T. It's useful when you want to create a new type with only specific properties from an existing type. Example: type Person = { name: string; age: number; address: string }; type NameAge = Pick<Person, 'name' | 'age'>; // Results in { name: string; age: number }
Recursive types are types that reference themselves in their definition. They're useful for representing tree-like data structures or nested objects. Example: type TreeNode<T> = { value: T; children?: TreeNode<T>[] }; This creates a type that can represent a tree structure where each node can have child nodes of the same type.
ReturnType<T> extracts the return type of a function type T. It's useful when you want to reference the return type of a function without explicitly defining it. Example: function createUser() { return { id: 1, name: 'John' } }; type User = ReturnType<typeof createUser>; // Type will be { id: number; name: string }
Readonly<T> creates a new type where all properties of T are readonly. It's useful when you want to ensure that properties can't be modified after initialization. Example: type Point = { x: number; y: number }; type ReadonlyPoint = Readonly<Point>; This prevents accidental mutations of object properties.
Lookup types allow you to extract the type of a property from another type using indexed access notation. Example: type Person = { age: number; name: string }; type AgeType = Person['age']; // type AgeType = number. This is useful when you need to reference the type of a specific property from an existing type.
Literal type widening occurs when TypeScript widens a literal type to its base type. Const assertions (using 'as const') prevent this widening and create readonly literal types. Example: let x = 'hello' // type is string (widened), but let x = 'hello' as const // type is 'hello' (literal). Const assertions are useful for creating precise literal types.
Extract<T, U> extracts from T all types that are assignable to U. It's useful for filtering union types. Example: type Numbers = Extract<string | number | boolean, number>; // Results in type Numbers = number. This utility helps create new types by filtering existing union types based on assignability.
Record<K,T> creates an object type with properties of type K and values of type T, while index signatures use [key: K]: T syntax. Record is more strict as it requires all keys to exist, while index signatures allow any key. Example: type StringMap = Record<string, string> vs type StringDict = { [key: string]: string }.
TypeScript classes are blueprints for creating objects that extend JavaScript classes with additional features like access modifiers (public, private, protected), parameter properties, abstract classes, and interface implementation. They provide compile-time type checking and better encapsulation. Example: class Person { private name: string; constructor(name: string) { this.name = name; } }
TypeScript supports three access modifiers: public (default, accessible everywhere), private (only within the declaring class), and protected (within declaring class and derived classes). These modifiers help enforce encapsulation and control access to class members. Example: class Employee { private salary: number; protected id: string; public name: string; }
Abstract classes are base classes that cannot be instantiated directly and may contain abstract methods that must be implemented by derived classes. They're defined using the 'abstract' keyword. They're useful for defining common behavior while forcing specific implementations in subclasses. Example: abstract class Animal { abstract makeSound(): void; move() { console.log('moving...'); } }
TypeScript supports single inheritance using the 'extends' keyword. A class can inherit properties and methods from another class, and can override methods from the parent class. Multiple inheritance is achieved through interfaces. Example: class Employee extends Person { constructor(name: string, private department: string) { super(name); } }
Parameter properties are a TypeScript shorthand that allows you to both declare and initialize class members in the constructor parameters. They're created by adding an access modifier to the parameter. Example: class Person { constructor(private name: string, public age: number) {} } This creates and initializes 'name' and 'age' properties automatically.
Classes can implement interfaces using the 'implements' keyword. The class must provide implementations for all properties and methods defined in the interface. Multiple interfaces can be implemented using comma separation. Example: interface Printable { print(): void; } class Document implements Printable { print() { console.log('printing...'); } }
Static members (properties and methods) belong to the class itself rather than instances of the class. They're defined using the 'static' keyword and are accessed using the class name. Example: class Calculator { static PI = 3.14159; static add(x: number, y: number): number { return x + y; } }
TypeScript supports JavaScript's get and set accessors with additional type safety. They allow you to add logic when accessing or modifying class properties. Example: class Circle { private _radius: number; get radius(): number { return this._radius; } set radius(value: number) { if (value >= 0) this._radius = value; } }
Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its parent class. TypeScript ensures type safety in overridden methods. Example: class Animal { makeSound() { return 'noise'; } } class Dog extends Animal { override makeSound() { return 'woof'; } }
Polymorphism in TypeScript can be achieved through method overriding and interfaces. It allows objects of different classes to be treated as objects of a common base class or interface. Example: interface Shape { area(): number; } class Circle implements Shape { area() { return Math.PI * r * r; } } class Rectangle implements Shape { area() { return w * h; } }
Private constructors prevent class instantiation from outside the class. They're commonly used in singleton pattern implementation or utility classes that shouldn't be instantiated. Example: class Utility { private constructor() {} static helper() { return 'help'; } }
Singleton pattern ensures a class has only one instance. In TypeScript, it's implemented using a private constructor and static instance. Example: class Singleton { private static instance: Singleton; private constructor() {} static getInstance(): Singleton { if (!Singleton.instance) { Singleton.instance = new Singleton(); } return Singleton.instance; } }
The 'readonly' modifier makes class properties immutable after initialization. They must be initialized at declaration or in the constructor. Example: class Config { readonly apiKey: string; constructor(key: string) { this.apiKey = key; } } Once set, readonly properties cannot be changed.
TypeScript supports method overloading through function declarations with different parameter types or numbers. Implementation is done with a single function that handles all overloads. Example: class Calculator { add(x: number, y: number): number; add(x: string, y: string): string; add(x: any, y: any): any { return x + y; } }
Abstract methods are methods declared in abstract classes without implementation. They must be implemented by derived classes. They're used when a base class knows a method is needed but specific implementation depends on the derived class. Example: abstract class Shape { abstract calculateArea(): number; }
The Factory pattern provides an interface for creating objects without specifying exact classes. In TypeScript, it's implemented using abstract classes or interfaces with factory methods. Example: abstract class Creator { abstract createProduct(): Product; } class ConcreteCreator extends Creator { createProduct(): Product { return new ConcreteProduct(); } }
Protected constructors allow instantiation only from within the class itself or its derived classes. This pattern is useful for creating base classes that shouldn't be instantiated directly but can be extended. Example: class Base { protected constructor() {} } class Derived extends Base { constructor() { super(); } }
Mixins are a way to combine multiple classes into one. TypeScript implements mixins using class expressions and a helper function. Example: type Constructor<T = {}> = new (...args: any[]) => T; function Timestamped<TBase extends Constructor>(Base: TBase) { return class extends Base { timestamp = Date.now(); }; }
Index signatures in classes allow you to define dynamic properties with a specific type. They're defined using [key: string]: type syntax. Example: class Dictionary { [key: string]: string; constructor() {} add(key: string, value: string) { this[key] = value; } }
The Observer pattern is implemented using interfaces for Subject and Observer, with the Subject maintaining a list of observers and notifying them of changes. Example: interface Observer { update(data: any): void; } class Subject { private observers: Observer[] = []; addObserver(observer: Observer) { this.observers.push(observer); } notify(data: any) { this.observers.forEach(observer => observer.update(data)); } }
Composition involves creating complex objects by combining simpler ones, while inheritance creates relationships between classes. Composition is often preferred as it provides more flexibility and looser coupling. Example of composition: class Car { private engine: Engine; private wheels: Wheel[]; constructor() { this.engine = new Engine(); this.wheels = [new Wheel(), new Wheel(), new Wheel(), new Wheel()]; } }
The 'super' keyword is used to call methods or access properties on the parent class. It's commonly used in constructors of derived classes and when overriding methods. Example: class Child extends Parent { constructor(name: string) { super(name); } override getName(): string { return 'Child: ' + super.getName(); } }
Type guards in class hierarchies help narrow down types when working with inheritance. The instanceof operator is commonly used as a type guard. Example: class Animal {} class Dog extends Animal { bark() {} } function makeNoise(animal: Animal) { if (animal instanceof Dog) { animal.bark(); } }
TypeScript offers several ways to define function types: 1) Using interface: interface Func { (x: number): number; }, 2) Type alias: type Func = (x: number) => number, 3) Arrow function syntax: let func: (x: number) => number, 4) Method signature in object type: { method(x: number): number }. Each approach has specific use cases and benefits.
Optional parameters are marked with a '?' after the parameter name. They must come after required parameters. Example: function greet(name: string, greeting?: string) { return greeting ? `${greeting}, ${name}!` : `Hello, ${name}!`; }. Optional parameters can be omitted when calling the function.
Rest parameters allow functions to accept an indefinite number of arguments as an array. They're denoted by '...' before the parameter name. Example: function sum(...numbers: number[]): number { return numbers.reduce((total, n) => total + n, 0); }. Rest parameters must be the last parameter in a function.
Function overloading involves declaring multiple function signatures followed by a single implementation that handles all cases. Example: function add(a: string, b: string): string; function add(a: number, b: number): number; function add(a: any, b: any): any { return a + b; }. The implementation must be compatible with all overload signatures.
Generic functions allow creating reusable functions that can work with multiple types while maintaining type safety. They use type parameters denoted by <T>. Example: function identity<T>(arg: T): T { return arg; }. Generic functions provide type inference and type safety while being flexible.
Default parameters provide fallback values for parameters that are not passed to the function. Example: function greet(name: string, greeting: string = 'Hello') { return `${greeting}, ${name}!`; }. Default parameters can be of any type and can reference other parameters.
Arrow functions provide a concise syntax for function expressions and lexically bind 'this'. Example: let add = (a: number, b: number): number => a + b;. They differ from regular functions in that they don't have their own 'this', arguments object, super, or new.target bindings.
TypeScript allows typing 'this' parameter as the first parameter in function declarations. Example: function example(this: void) {} ensures the function doesn't use 'this'. For methods: function example(this: MyClass) {} ensures 'this' is of type MyClass. This helps catch incorrect 'this' usage.
Higher-order functions are functions that take other functions as parameters or return functions. Example: function map<T, U>(arr: T[], fn: (item: T) => U): U[] { return arr.map(fn); }. They're fundamental for functional programming and enable composition and abstraction.
Callbacks can be typed using function types or interfaces. Example: interface Callback { (error: Error | null, result?: string): void; } function fetchData(callback: Callback) {}. This ensures type safety when working with asynchronous operations and event handlers.
Function type assertions allow you to tell TypeScript that a function is of a specific type. Example: const myFunc = ((x: string) => parseInt(x)) as (x: string) => number;. They should be used sparingly as they bypass TypeScript's type checking.
Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument. Example: function curry<T,U,V>(f: (x: T, y: U) => V): (x: T) => (y: U) => V { return x => y => f(x, y); }. This enables partial application and function composition.
Function type intersections combine multiple function types into one that must satisfy all types. Example: type Combined = ((x: string) => string) & ((x: number) => number);. The resulting function must handle both string and number inputs with corresponding return types.
Async functions are typed with Promise<T> where T is the type of the resolved value. Example: async function getData(): Promise<string> { const response = await fetch('api'); return response.text(); }. TypeScript ensures type safety for async/await operations.
Generator functions return iterables using the function* syntax and yield keyword. Example: function* range(start: number, end: number): Generator<number> { for(let i = start; i <= end; i++) yield i; }. They're useful for creating iterables and handling sequences.
Method chaining involves returning this from methods to enable consecutive calls. Example: class Calculator { private value: number = 0; add(n: number): this { this.value += n; return this; } multiply(n: number): this { this.value *= n; return this; } }
Function type unions allow a function to have multiple possible types. Example: type StringOrNumberFunc = ((x: string) => string) | ((x: number) => number);. When using such functions, TypeScript ensures type safety by requiring type checking or overloads.
Function decorators are typed as functions that take a function descriptor and return a new descriptor or value. Example: function logged(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${propertyKey}`); return original.apply(this, args); }; }
TypeScript can infer function return types and sometimes parameter types based on usage and context. Example: function map<T, U>(array: T[], func: (item: T) => U): U[] { return array.map(func); }. TypeScript infers the return type U[] based on the mapping function.
Function composition combines multiple functions into a single function, applying them in sequence. Example: const compose = <T,U,V>(f: (x: U) => V, g: (x: T) => U) => (x: T): V => f(g(x));. This enables building complex functions from simpler ones.
Contextual typing allows TypeScript to infer types based on the context where a function is used. Example: const numbers = [1, 2, 3].map(n => n * 2); TypeScript infers that the arrow function parameter n is a number based on the array's type.
Event handlers are typically typed using the appropriate event interface. Example: function handleClick(event: React.MouseEvent<HTMLButtonElement>) { console.log(event.currentTarget); }. This ensures type safety when working with DOM events and framework-specific event systems.
Call signatures define how a function can be called, while construct signatures define how a function can be used with 'new'. Example: interface CallableConstructable { (x: string): string; new(x: string): object; }. This allows typing functions that can be both called and constructed.
Function properties are typed using regular property syntax on function types. Example: interface FunctionWithProps { (x: number): number; defaultValue: number; }. This allows creating functions with additional properties or methods.
Function type constraints limit what types can be used with generic functions. Example: function longest<T extends { length: number }>(a: T, b: T): T { return a.length >= b.length ? a : b; }. This ensures the generic type T has a length property.
Generics are a way to create reusable components that can work with multiple types while maintaining type safety. They allow you to write functions, classes, and interfaces that can work with any data type while preserving type information. Example: function identity<T>(arg: T): T { return arg; }. Generics provide type safety, code reusability, and prevent code duplication.
Generic constraints limit what types can be used with a generic type using the 'extends' keyword. Example: function getLength<T extends { length: number }>(arg: T): number { return arg.length; }. This ensures that the generic type T must have a length property. Constraints help enforce type safety while maintaining flexibility.
Generic interfaces are interfaces that can work with multiple types. Example: interface Container<T> { value: T; getValue(): T; }. Implementation: class NumberContainer implements Container<number> { constructor(public value: number) {} getValue(): number { return this.value; } }. They enable type-safe, reusable interface definitions.
Conditional types select a type based on a condition, using syntax similar to ternary operators: T extends U ? X : Y. Example: type NonNullable<T> = T extends null | undefined ? never : T. They're powerful for creating complex type transformations and can be used with mapped types and unions.
TypeScript can infer generic types from usage context. Example: function identity<T>(arg: T): T { return arg; } let output = identity('hello'); // T is inferred as string. Type inference reduces verbosity while maintaining type safety. The compiler uses argument types to determine generic type parameters.
Type parameters are placeholders for types in generic definitions, conventionally denoted by T, U, K, V, etc. Example: function swap<T, U>(tuple: [T, U]): [U, T] { return [tuple[1], tuple[0]]; }. They can have constraints, defaults, and be used in multiple places within the generic definition.
Generic classes use type parameters to create flexible, reusable class definitions. Example: class Stack<T> { private items: T[] = []; push(item: T) { this.items.push(item); } pop(): T | undefined { return this.items.pop(); } }. They maintain type safety while working with different data types.
Mapped types create new types by transforming properties of existing types. Example: type Readonly<T> = { readonly [P in keyof T]: T[P] }. They can add/remove modifiers (readonly, optional) and transform property types. Mapped types are powerful for type transformations and creating utility types.
The infer keyword extracts type information within conditional types. Example: type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any. This extracts the return type R from a function type. infer is powerful for type inference and extraction in complex scenarios.
Generic type defaults provide fallback types when no type argument is specified. Example: interface Container<T = string> { value: T; }. When no type is provided, string is used: let container: Container = { value: 'hello' }. They help make generic types more convenient to use.
Multiple type parameters allow generics to work with multiple types simultaneously. Example: function pair<T, U>(first: T, second: U): [T, U] { return [first, second]; }. The order and naming of type parameters matter for readability and understanding the relationship between types.
Index types allow type-safe access to properties using dynamic keys. Example: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }. This ensures type safety when accessing object properties dynamically.
Generic type guards combine generics with type predicates. Example: function isOfType<T>(value: any, property: keyof T): value is T { return property in value; }. They provide type-safe runtime checks while maintaining generic type information.
Union and intersection types can be used with generics to create flexible type combinations. Example: function process<T, U>(value: T | U): T & U { ... }. This allows working with types that can be either T or U while producing a type that has properties of both.
Generic type aliases create reusable type definitions with type parameters. Example: type Result<T> = { success: boolean; data?: T; error?: string; }. They provide flexibility while maintaining type safety and can be used with unions, intersections, and mapped types.
Generic constraints can use union and intersection types to create complex type requirements. Example: function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 }; }. This ensures type parameters meet specific criteria while allowing flexible combinations.
Branded types are nominal types created using intersection with unique symbols. Example: type Brand<T, B> = T & { __brand: B }; type USD = Brand<number, 'USD'>. They provide type safety by preventing mixing of similarly structured but semantically different types.
The keyof operator can be used with generics to create type-safe property access. Example: function getNestedValue<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }. This ensures type safety when accessing object properties dynamically.
Variadic tuple types allow working with tuples of variable length in a type-safe way. Example: type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]. They're useful for type-safe array operations and function parameter handling.
Template literal types can be combined with generics to create dynamic string types. Example: type PropEventType<T extends string> = `${T}Changed`; This creates new string literal types based on generic parameters.
Generic type assertions allow specifying type parameters when using type assertions. Example: function create<T>(factory: { new(): T }): T { return new factory() as T; }. They provide type safety when working with dynamic type creation.
Generic default types can be combined with constraints to provide flexible yet safe defaults. Example: interface Container<T extends object = { id: string }> { data: T; }. This ensures the default type meets the constraint while allowing other compatible types.
Distributive conditional types apply conditions to each member of a union type. Example: type ToArray<T> = T extends any ? T[] : never; type NumberOrStringArray = ToArray<number | string>; // number[] | string[]. They're useful for transforming union types.
Generic event emitters provide type safety for event handling. Example: class EventEmitter<Events extends Record<string, any>> { emit<E extends keyof Events>(event: E, data: Events[E]): void { ... } }. This ensures event names and data types match at compile time.
Key differences include: 1) Interfaces are extendable through declaration merging while type aliases are not, 2) Interfaces can only describe object shapes while type aliases can create unions, primitives, and tuples, 3) Interfaces are better for defining contracts in object-oriented programming while type aliases are preferable for functional programming patterns. Example: interface vs type Point = { x: number; y: number; }
Interfaces can inherit from other interfaces using the 'extends' keyword. They can extend multiple interfaces, creating a combination of all properties. Example: interface Shape { color: string; } interface Circle extends Shape { radius: number; } A class implementing Circle must provide both color and radius properties.
Optional properties in interfaces are marked with a '?' symbol after the property name. They don't need to be implemented when using the interface. Example: interface User { name: string; email?: string; } This means email is optional and can be undefined.
Readonly properties are marked with the 'readonly' modifier and cannot be changed after initialization. Example: interface Point { readonly x: number; readonly y: number; } This ensures immutability of these properties after they are set initially.
Index signatures allow interfaces to describe objects with dynamic property names. Example: interface StringMap { [key: string]: string; } This means the object can have any number of string properties, where both the key and value are strings.
Function types in interfaces can be defined using call signatures. Example: interface Calculation { (x: number, y: number): number; } This defines an interface for a function that takes two numbers and returns a number. You can also use method syntax: interface Math { add(x: number, y: number): number; }
Declaration merging allows multiple interface declarations with the same name to be automatically combined. Example: interface User { name: string; } interface User { age: number; } Results in an interface requiring both name and age. This is unique to interfaces and not available with type aliases.
Interfaces can use generic type parameters to create flexible, reusable definitions. Example: interface Container<T> { value: T; getValue(): T; } This allows the interface to work with any type while maintaining type safety.
Hybrid types combine function types with object properties. Example: interface Counter { (start: number): string; interval: number; reset(): void; } This creates an interface that can be both called as a function and has properties/methods.
An interface can extend multiple interfaces using comma-separated names. Example: interface Shape { color: string; } interface Sizeable { size: number; } interface Circle extends Shape, Sizeable { radius: number; } This combines all properties from the extended interfaces.
Recursive types are interfaces that reference themselves in their definition. Example: interface TreeNode { value: string; children?: TreeNode[]; } This creates a tree-like structure where each node can have child nodes of the same type.
Mapped types can be used to transform interface properties systematically. Example: type Readonly<T> = { readonly [P in keyof T]: T[P] }; interface User { name: string; age: number; } type ReadonlyUser = Readonly<User>;
Utility types provide common type transformations for interfaces. Example: interface User { name: string; age?: number; } type RequiredUser = Required<User>; type PartialUser = Partial<User>; These transform optional properties to required and vice versa.
Method overloading in interfaces is achieved by declaring multiple function signatures. Example: interface Document { createElement(tagName: any): any; createElement(tagName: 'div'): HTMLDivElement; createElement(tagName: 'span'): HTMLSpanElement; }
Union types (|) allow a value to be one of several types, while intersection types (&) combine multiple types. Example: interface Bird { fly(): void; } interface Fish { swim(): void; } type Pet = Bird | Fish; type Amphibian = Bird & Fish;
Type guards help narrow down interface types in conditional blocks. Example: interface Bird { fly(): void; } interface Fish { swim(): void; } function isBird(pet: Bird | Fish): pet is Bird { return (pet as Bird).fly !== undefined; }
Computed properties allow property names to be expressions. Example: type Events = 'click' | 'focus'; interface Handlers { [K in Events]: (event: any) => void; } This creates properties for each event type automatically.
Classes can implement interfaces using the 'implements' keyword. Example: interface Printable { print(): void; } class Document implements Printable { print() { console.log('printing...'); } } The class must provide implementations for all interface members.
Literal types specify exact values that a property must have. Example: interface Config { mode: 'development' | 'production'; port: 80 | 443; } This ensures properties can only have specific values.
Interfaces can be used as constraints for generic types. Example: interface Lengthwise { length: number; } function logLength<T extends Lengthwise>(arg: T): number { return arg.length; } This ensures the generic type has a length property.
Optional methods in interfaces are marked with '?' and don't need to be implemented. Example: interface Logger { log(message: string): void; error?(error: Error): void; } This makes the error method optional.
Interfaces can describe array-like structures using index signatures or extending Array. Example: interface StringArray { [index: number]: string; length: number; } interface MyArray<T> extends Array<T> { getFirst(): T; }
Constructor signatures define how a class constructor should look. Example: interface ClockConstructor { new (hour: number, minute: number): ClockInterface; } This ensures classes implementing the interface have the specified constructor.
Interfaces can define multiple call signatures for function overloading. Example: interface StringNumberFunction { (str: string): number; (str: string, radix: number): number; } This allows functions to be called with different parameter combinations.
Interfaces can define static members that must be implemented by the class itself rather than instances. Example: interface ClockConstructor { new (): ClockInterface; currentTime: Date; } The currentTime property must be implemented as a static property.
ES modules are the standard JavaScript module system that TypeScript supports. They use import/export syntax and provide better code organization and dependency management. Unlike namespaces (internal modules), ES modules are file-based, support better tree-shaking, and are the preferred way to organize code. Example: export class MyClass {} and import { MyClass } from './myModule';
TypeScript supports various export/import syntaxes: 1) Named exports: export { MyClass }; 2) Default exports: export default MyClass; 3) Import named: import { MyClass } from './module'; 4) Import default: import MyClass from './module'; 5) Import all: import * as module from './module'. Each type serves different purposes in module organization.
Namespaces (formerly 'internal modules') are TypeScript's own module system for encapsulating code. They use the namespace keyword and are useful for avoiding name collisions in global scope. Example: namespace Mathematics { export function add(x: number, y: number): number { return x + y; } }. However, ES modules are generally preferred for modern applications.
Barrel exports consolidate multiple exports into a single entry point using an index.ts file. Example: export * from './math'; export * from './string'; export * from './array';. This simplifies imports by providing a single import point and helps manage large codebases by organizing related functionality.
Module augmentation allows you to add new declarations to existing modules. Example: declare module 'lodash' { interface LoDashStatic { myCustomFunction(arg: string): number; } }. This is useful for adding type definitions to existing JavaScript modules or extending third-party module declarations.
Circular dependencies occur when two modules import each other. They can be resolved by: 1) Restructuring code to avoid circularity, 2) Using interfaces instead of concrete implementations, 3) Moving shared code to a separate module, 4) Using import type for type-only imports. Example: import type { User } from './user' instead of import { User } from './user'.
Ambient modules declare types for JavaScript modules that don't have TypeScript declarations. They use declare module syntax. Example: declare module 'my-library' { export function doSomething(): void; }. They're commonly used in .d.ts files to provide type information for JavaScript libraries.
Namespace merging allows combining multiple namespace declarations with the same name. Example: namespace Animals { export class Dog {} } namespace Animals { export class Cat {} }. This creates a single namespace containing both Dog and Cat classes. It's similar to interface merging but for namespaces.
TypeScript supports several module resolution strategies: 1) Classic: Legacy resolution for AMD/System.js, 2) Node: Follows Node.js module resolution, 3) baseUrl: Resolves modules relative to a base directory, 4) paths: Allows custom module path mapping. These are configured in tsconfig.json using the moduleResolution option.
Dynamic imports allow loading modules on demand using import() syntax. Example: async function loadModule() { const module = await import('./myModule'); module.doSomething(); }. This enables code-splitting and lazy loading of modules for better performance.
Declaration files (.d.ts) contain type information for JavaScript modules. They define the shape of modules without implementation details. Example: declare module 'my-module' { export interface Config { debug: boolean; } export function initialize(config: Config): void; }. They enable TypeScript to understand external JavaScript libraries.
Re-exports allow you to export items from other modules. Syntax includes: export { Something } from './other', export * from './other', and export { Something as OtherThing } from './other'. This helps create clean public APIs and organize code hierarchy.
Best practices include: 1) One class/feature per file, 2) Use barrel exports (index.ts) for related features, 3) Keep modules small and focused, 4) Use meaningful file names matching exported content, 5) Group related functionality in directories, 6) Use clear import paths, 7) Avoid circular dependencies. This improves code maintainability and readability.
Module path aliases are configured in tsconfig.json using the paths option. Example: { 'compilerOptions': { 'baseUrl': '.', 'paths': { '@app/*': ['src/app/*'] } } }. This allows using @app/feature instead of relative paths, making imports cleaner and more maintainable.
Internal modules (namespaces) use the namespace keyword and are TypeScript-specific. External modules (ES modules) use import/export syntax and are the standard JavaScript module system. Example: namespace MyNamespace {} vs. export class MyClass {}. ES modules are preferred for modern applications.
Namespaces use export keyword for public members and can be accessed using dot notation. Example: namespace Math { export function add(x: number, y: number): number { return x + y; } } Usage: Math.add(1, 2). For cross-file namespaces, use /// <reference path='./math.ts' /> syntax.
Synthetic default imports allow importing modules that don't have explicit default exports as if they did. Controlled by esModuleInterop compiler option. Example: import React from 'react' works even if React uses module.exports = React. This improves compatibility with CommonJS modules.
Module side effects are handled using import statements without bindings. Example: import './polyfills'; This executes the module code without importing any values. It's useful for polyfills, global styles, or other code that needs to run during module initialization.
Global modules are ambient modules that add types to the global scope. Example: declare global { interface Window { myCustomProperty: string; } }. They're useful for extending global objects or declaring global variables, but should be used sparingly to avoid polluting global namespace.
Type-only imports/exports are used for importing/exporting types without value emissions. Syntax: import type { MyType } from './types'; export type { MyType }. This helps reduce bundle size by removing type information during compilation while maintaining type safety.
Lazy loading patterns include: 1) Dynamic imports: import('./module'), 2) Route-based splitting: loadChildren in Angular routes, 3) Component-based splitting in React.lazy(). Example: const MyComponent = React.lazy(() => import('./MyComponent')). This improves initial load time by loading modules on demand.
Monorepo module resolution involves: 1) Using project references in tsconfig.json, 2) Configuring path aliases for packages, 3) Setting up proper build order, 4) Managing shared dependencies. Example: { 'references': [{ 'path': '../common' }], 'paths': { '@org/*': ['packages/*/src'] } }.
Module augmentation can be done through: 1) Declaration merging: declare module 'module' {}, 2) Global augmentation: declare global {}, 3) Namespace augmentation: namespace NS {}. Example: declare module 'express-session' { interface SessionData { userId: string; } }
Module resolution fallbacks are configured through: 1) moduleResolution in tsconfig.json, 2) baseUrl and paths for custom mappings, 3) TypeScript path mapping patterns like * and **. Example: { 'paths': { '*': ['node_modules/*', 'fallback/*'] } }. This helps handle different module formats and locations.
Decorators are special declarations that can be attached to class declarations, methods, properties, or parameters. They are enabled by setting 'experimentalDecorators: true' in tsconfig.json. Example: @decorator class Example {}. Decorators provide a way to add annotations and metadata to existing code.
TypeScript supports five types of decorators: 1) Class decorators (@classDecorator), 2) Method decorators (@methodDecorator), 3) Property decorators (@propertyDecorator), 4) Accessor decorators (@accessorDecorator), and 5) Parameter decorators (@parameterDecorator). Each type receives different arguments and can be used for different purposes.
Class decorators are declared before a class declaration and receive the constructor as an argument. Example: function logger(target: Function) { console.log(`Creating class: ${target.name}`); } @logger class Example {}. They can modify or replace the class definition and are useful for adding metadata or behavior to classes.
Method decorators are declared before method declarations and receive three arguments: target (prototype), propertyKey (method name), and descriptor (property descriptor). Example: function log(target: any, key: string, descriptor: PropertyDescriptor) { // Original method const original = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${key}`); return original.apply(this, args); }; }
reflect-metadata is a library that adds a polyfill for the Metadata Reflection API. It's used with decorators to add and read metadata about classes, methods, and properties. Enable with 'emitDecoratorMetadata: true' in tsconfig.json. Example: import 'reflect-metadata'; @Reflect.metadata('role', 'admin') class User {}
Property decorators receive two arguments: target (prototype) and propertyKey (property name), unlike method decorators which also receive a descriptor. Example: function validate(target: any, key: string) { let value = target[key]; Object.defineProperty(target, key, { get: () => value, set: (newValue) => { if (!newValue) throw new Error('Value cannot be null'); value = newValue; } }); }
Decorator factories are functions that return decorators and allow customization through parameters. Example: function log(prefix: string) { return function(target: any) { console.log(`${prefix}: ${target.name}`); }; } @log('MyApp') class Example {}. They provide a way to customize decorator behavior.
Parameter decorators are applied to method parameters and receive three arguments: target (prototype), methodName, and parameterIndex. Example: function required(target: Object, propertyKey: string, parameterIndex: number) { const requiredParams = Reflect.getMetadata('required', target, propertyKey) || []; requiredParams.push(parameterIndex); Reflect.defineMetadata('required', requiredParams, target, propertyKey); }
Accessor decorators are applied to getter/setter declarations and receive three arguments similar to method decorators. Example: function enumerable(value: boolean) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.enumerable = value; }; } They're useful for modifying accessor behavior.
Multiple decorators can be applied to a declaration in sequence. They are evaluated in reverse order (bottom to top). Example: @f @g class C {}. Evaluation order: g then f. For decorator factories: @f() @g() class C {}, the factories are evaluated in order (f then g) but the decorators in reverse order.
Metadata allows storing additional information about classes, methods, and properties that can be accessed at runtime. It's enabled with reflect-metadata. Example: Reflect.defineMetadata('validation', { required: true }, target, propertyKey); const metadata = Reflect.getMetadata('validation', target, propertyKey);
Validation decorators can be implemented using property or parameter decorators with metadata. Example: function required(target: any, propertyKey: string) { const validationMetadata = { required: true }; Reflect.defineMetadata('validation', validationMetadata, target, propertyKey); } class User { @required name: string; }
Common use cases include: 1) Logging and monitoring, 2) Property validation, 3) Dependency injection, 4) Method memoization, 5) Access control and authorization, 6) Class and property transformation, 7) API endpoint definition (e.g., in NestJS), 8) Observable properties (e.g., in Angular), 9) Type serialization/deserialization.
Decorators can implement dependency injection by using metadata to store and retrieve dependencies. Example: function Injectable() { return function(target: any) { Reflect.defineMetadata('injectable', true, target); }; } @Injectable() class Service {} This pattern is used in frameworks like Angular and NestJS.
Limitations include: 1) Experimental feature requiring compiler flag, 2) Can't decorate declarations in .d.ts files, 3) Limited to specific declaration types (class, method, property, parameter), 4) Can't access decorated values during declaration phase, 5) No direct way to decorate local variables or function declarations.
Memoization decorators cache function results based on arguments. Example: function memoize(target: any, key: string, descriptor: PropertyDescriptor) { const original = descriptor.value; const cache = new Map(); descriptor.value = function(...args: any[]) { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = original.apply(this, args); cache.set(key, result); return result; }; }
Design-time type metadata is automatically generated when emitDecoratorMetadata is enabled. It includes type information for parameters, return types, and properties. Example: const paramTypes = Reflect.getMetadata('design:paramtypes', target, key); This is used by frameworks for dependency injection and validation.
Method override decorators can verify proper method overriding. Example: function override(target: any, key: string, descriptor: PropertyDescriptor) { const baseClass = Object.getPrototypeOf(target); if (!baseClass[key]) throw new Error(`${key} doesn't override any base method`); return descriptor; } class Child extends Parent { @override method() {} }
Best practices include: 1) Use decorator factories for configuration, 2) Keep decorators focused and single-purpose, 3) Handle errors gracefully, 4) Use meaningful names that describe behavior, 5) Document decorator requirements and effects, 6) Avoid side effects in decorator evaluation, 7) Use metadata for storing configuration, 8) Consider performance implications.
Lazy initialization decorators delay property initialization until first access. Example: function lazy<T>(initializer: () => T) { return function(target: any, key: string) { let value: T; Object.defineProperty(target, key, { get: () => { if (!value) value = initializer(); return value; } }); }; } class Example { @lazy(() => expensiveOperation()) value: string; }
Async operations in decorators require special handling since decorators run during class definition. Example: function asyncInit() { return function(target: any) { return class extends target { async init() { await super.init(); // Additional async initialization } }; }; } @asyncInit() class Service { async init() { /* ... */ } }
Key differences include: 1) Syntax variations, 2) TypeScript decorators are experimental while ES decorators are part of the standard, 3) Different metadata handling, 4) ES decorators have additional capabilities like decorating object literals, 5) TypeScript decorators may need updates to align with ES decorator standard when finalized.
Custom error types can be created by extending the Error class. Example: class ValidationError extends Error { constructor(message: string) { super(message); this.name = 'ValidationError'; Object.setPrototypeOf(this, ValidationError.prototype); } } This allows for type-safe error handling and custom error properties.
Union types in error handling allow functions to return either a success value or an error type. Example: type Result<T> = { success: true; data: T; } | { success: false; error: Error; }. This pattern enables type-safe error handling without throwing exceptions: function process(): Result<string> { try { return { success: true, data: 'processed' }; } catch (e) { return { success: false, error: e instanceof Error ? e : new Error(String(e)) }; } }
Async errors can be handled using try-catch with async/await or .catch() with Promises. Example: async function handleAsync() { try { await riskyOperation(); } catch (error) { if (error instanceof NetworkError) { // Handle network errors } else if (error instanceof ValidationError) { // Handle validation errors } throw error; // Re-throw unhandled errors } }
Type guards help narrow down error types for proper handling. Example: function isNetworkError(error: unknown): error is NetworkError { return error instanceof NetworkError; } try { // risky operation } catch (error) { if (isNetworkError(error)) { console.log(error.statusCode); // TypeScript knows error is NetworkError } }
The 'unknown' type is safer than 'any' for error handling as it requires type checking before use. Example: function handleError(error: unknown) { if (error instanceof Error) { console.log(error.message); } else if (typeof error === 'string') { console.log(error); } else { console.log('Unknown error occurred'); } }
TypeScript debugging tools include: 1) Source maps for debugging compiled code, 2) VS Code's built-in debugger, 3) Chrome DevTools with source maps, 4) debugger statement, 5) console methods (log, warn, error, trace), 6) TypeScript compiler flags like --noEmitOnError, 7) Jest debugger for testing. Configuration in launch.json: { 'type': 'node', 'request': 'launch', 'sourceMaps': true }
Error boundaries are implemented using class components with error handling lifecycle methods. Example: class ErrorBoundary extends React.Component<Props, State> { static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { logErrorToService(error, errorInfo); } } This catches and handles errors in child components.
Source maps enable debugging of TypeScript code directly, even though the browser runs compiled JavaScript. They map positions in the compiled code to the original TypeScript. Enable with 'sourceMap: true' in tsconfig.json. This allows setting breakpoints in TypeScript files and seeing TypeScript variables during debugging.
Type assertions should be used carefully in error handling to maintain type safety. Example: function handleError(error: unknown) { if (error instanceof Error) { const customError = error as CustomError; // Only assert after instanceof check if (customError.code) { // Handle custom error } } }
Best practices include: 1) Use custom error classes for specific error types, 2) Implement type-safe error handling with union types, 3) Always check error types before using their properties, 4) Use async/await with try-catch for async operations, 5) Implement proper error logging and monitoring, 6) Use error boundaries in React applications, 7) Avoid using 'any' for error types, prefer 'unknown'.
Error logging can be implemented using a centralized error logging service. Example: class ErrorLogger { static log(error: Error, context?: object) { const errorLog = { timestamp: new Date(), name: error.name, message: error.message, stack: error.stack, context }; // Send to logging service or store locally console.error(errorLog); } }
throw is used for synchronous error handling and immediately stops execution, while reject is used in Promises for asynchronous error handling. Example: function sync() { throw new Error('Sync error'); } async function async() { return Promise.reject(new Error('Async error')); } throw creates an exception, reject creates a rejected Promise.
TypeScript tests can be debugged using: 1) Jest's debugger with ts-jest, 2) VS Code's debug configuration for tests, 3) Chrome DevTools with karma. Example launch.json: { 'type': 'node', 'request': 'launch', 'name': 'Debug Tests', 'program': '${workspaceFolder}/node_modules/jest/bin/jest', 'args': ['--runInBand'] }
Retry logic can be implemented using recursive functions or libraries with proper error handling. Example: async function retryOperation<T>(operation: () => Promise<T>, maxRetries: number): Promise<T> { try { return await operation(); } catch (error) { if (maxRetries > 0) { await delay(1000); return retryOperation(operation, maxRetries - 1); } throw error; } }
Conditional types help create type-safe error handling patterns. Example: type ErrorResponse<T> = T extends Error ? { error: T; data: null; } : { error: null; data: T; }; function handleResult<T>(result: T): ErrorResponse<T> { return result instanceof Error ? { error: result, data: null } : { error: null, data: result }; }
Promise rejections can be handled using .catch(), try-catch with async/await, or global handlers. Example: window.onunhandledrejection = (event: PromiseRejectionEvent) => { console.error('Unhandled promise rejection:', event.reason); event.preventDefault(); }; This ensures all rejected Promises are handled.
Discriminated unions provide type-safe error handling by using a common property to discriminate between success and error states. Example: type Result<T> = { kind: 'success'; value: T; } | { kind: 'error'; error: Error; }; This enables exhaustive checking of all possible states.
Memory leaks can be debugged using: 1) Chrome DevTools Memory panel, 2) Heap snapshots, 3) Memory allocation timeline, 4) Node.js --inspect flag. Example: Taking heap snapshots: const heapSnapshot = require('heapdump'); heapSnapshot.writeSnapshot(`${Date.now()}.heapsnapshot`); Then analyze using Chrome DevTools.
Decorators can implement error handling through method wrapping. Example: function catchErrors() { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = async function(...args: any[]) { try { return await original.apply(this, args); } catch (error) { ErrorLogger.log(error); throw error; } }; }; }
Circuit breakers prevent cascading failures by stopping operations after too many errors. Example: class CircuitBreaker { private failures = 0; private readonly threshold = 5; async execute<T>(operation: () => Promise<T>): Promise<T> { if (this.failures >= this.threshold) { throw new Error('Circuit breaker open'); } try { const result = await operation(); this.failures = 0; return result; } catch (error) { this.failures++; throw error; } } }
tsconfig.json is the main configuration file for TypeScript projects. Key components include: 1) compilerOptions for configuring the TypeScript compiler, 2) include/exclude for specifying which files to compile, 3) extends for inheriting configurations, 4) files for explicitly listing files to include. Example: { 'compilerOptions': { 'target': 'ES6', 'module': 'commonjs', 'strict': true }, 'include': ['src/**/*'], 'exclude': ['node_modules'] }
Key compiler options include: 1) target (specifies ECMAScript target version), 2) module (module code generation), 3) strict (enables all strict type checking options), 4) outDir (output directory), 5) rootDir (root directory of source files), 6) sourceMap (generates source maps), 7) declaration (generates .d.ts files), 8) noImplicitAny (error on implied any types), 9) esModuleInterop (enables interop between CommonJS and ES Modules)
Module resolution is configured through tsconfig.json options: 1) moduleResolution ('node' or 'classic'), 2) baseUrl (base directory for non-relative imports), 3) paths (path mapping for module aliases), 4) rootDirs (list of root folders). Example: { 'compilerOptions': { 'moduleResolution': 'node', 'baseUrl': './src', 'paths': { '@/*': ['*'] } } }
TypeScript integration with webpack requires: 1) ts-loader or babel-loader with @babel/preset-typescript, 2) source map configuration, 3) resolve extensions configuration. Example webpack config: { module: { rules: [{ test: /\.tsx?$/, use: 'ts-loader' }] }, resolve: { extensions: ['.tsx', '.ts', '.js'] } }
Project references allow splitting TypeScript projects into smaller pieces for better organization and build performance. They're configured using the references field in tsconfig.json. Example: { 'references': [{ 'path': '../common' }], 'compilerOptions': { 'composite': true } }. This enables incremental compilation and better project organization.
Different environments can be configured using: 1) Multiple tsconfig files (tsconfig.dev.json, tsconfig.prod.json), 2) Environment-specific compiler options, 3) Conditional types based on environment. Example: { 'extends': './tsconfig.base.json', 'compilerOptions': { 'sourceMap': true, 'declaration': false } }
Declaration files (.d.ts) provide type information for JavaScript code. They can be generated using the declaration compiler option. Example tsconfig.json: { 'compilerOptions': { 'declaration': true, 'declarationDir': './types' } }. They're useful for creating type definitions for JavaScript libraries or separating type declarations from implementation.
Strict type checking is configured using the strict flag and individual flags: 1) strictNullChecks, 2) strictFunctionTypes, 3) strictBindCallApply, 4) strictPropertyInitialization, 5) noImplicitAny, 6) noImplicitThis. Example: { 'compilerOptions': { 'strict': true, 'strictNullChecks': true } }
TypeScript tooling options include: 1) VS Code with built-in TypeScript support, 2) WebStorm with TypeScript integration, 3) ESLint with @typescript-eslint, 4) Prettier for code formatting, 5) TypeScript Language Service plugins. These provide features like IntelliSense, refactoring, and error checking.
Path aliases are configured using baseUrl and paths in tsconfig.json. Example: { 'compilerOptions': { 'baseUrl': '.', 'paths': { '@components/*': ['src/components/*'], '@utils/*': ['src/utils/*'] } } }. This enables using imports like import { Button } from '@components/Button';
Best practices include: 1) Using project references for large codebases, 2) Consistent file/folder structure, 3) Proper module organization, 4) Shared tsconfig.json settings, 5) Clear naming conventions, 6) Separation of concerns, 7) Using barrel exports (index.ts files), 8) Proper type declaration organization.
Monorepo configuration involves: 1) Using project references, 2) Setting up shared configurations, 3) Configuring workspace dependencies, 4) Using tools like Lerna or Nx. Example: Root tsconfig.json with references to package configs and shared compiler options. { 'references': [{ 'path': 'packages/common' }, { 'path': 'packages/client' }] }
Language Service plugins extend TypeScript's language service functionality. They can add custom type checking, code completion, and refactoring capabilities. Example configuration: { 'compilerOptions': { 'plugins': [{ 'name': 'typescript-styled-plugin' }] } }. Common uses include styled-components integration and custom lint rules.
Incremental compilation is configured using: 1) incremental flag, 2) tsBuildInfoFile option, 3) composite project setting. Example: { 'compilerOptions': { 'incremental': true, 'tsBuildInfoFile': './buildcache/front-end.tsbuildinfo' } }. This improves build performance by reusing previous compilation results.
TypeScript supports multiple module systems: 1) ES Modules (import/export), 2) CommonJS (require/exports), 3) AMD, 4) UMD, 5) System. Configure using the module compiler option. Example: { 'compilerOptions': { 'module': 'esnext', 'moduleResolution': 'node' } }. Choice depends on target environment and compatibility requirements.
Source maps are configured using: 1) sourceMap compiler option, 2) sourceRoot option, 3) mapRoot option. Example: { 'compilerOptions': { 'sourceMap': true, 'sourceRoot': '/', 'mapRoot': 'dist/maps' } }. They enable debugging TypeScript code directly in browsers or editors.
Assets can be handled through: 1) Declaration files for non-code assets, 2) Module declarations for imports, 3) Webpack loaders, 4) Custom type definitions. Example: declare module '*.png' { const content: string; export default content; }. This enables type-safe handling of various resource types.
Testing configuration includes: 1) Separate tsconfig.test.json, 2) Jest configuration with ts-jest, 3) Test-specific module resolution, 4) Test file patterns. Example: { 'extends': './tsconfig.base.json', 'compilerOptions': { 'types': ['jest'], 'esModuleInterop': true } }. This ensures proper testing setup with TypeScript.
TypeScript supports different build modes: 1) Regular compilation (tsc), 2) Watch mode (tsc -w), 3) Project references build (tsc -b), 4) Incremental builds. Example using build mode: tsc -b src/tsconfig.json --verbose. Each mode serves different development scenarios and requirements.
Configuration varies by bundler: 1) Webpack: ts-loader or babel-loader, 2) Rollup: @rollup/plugin-typescript, 3) Parcel: Built-in TypeScript support, 4) esbuild: Native TypeScript support. Example Rollup config: import typescript from '@rollup/plugin-typescript'; export default { plugins: [typescript()] }
Understand interfaces, generics, unions, and type inference.
Work on creating flexible and reusable type definitions.
Explore decorators, namespaces, and module systems.
Expect questions about type safety and code organization.
Join thousands of successful candidates preparing with Stark.ai. Start practicing TypeScript questions, mock interviews, and more to secure your dream role.
Start Preparing now