typescript_type_system 48 Q&As

TypeScript Type System FAQ & Answers

48 expert TypeScript Type System answers researched from official documentation. Every answer cites authoritative sources you can verify.

narrowing

12 questions
A

Control flow analysis (CFA) narrows types based on code structure. After if (typeof x === 'string'), TypeScript knows x is string in that branch. CFA handles: typeof checks, instanceof, truthiness, equality (===, !==), in operator, discriminated unions. TypeScript builds a control flow graph during binding, then lazily evaluates types from this graph when needed. Works backward from variable reference to root.

99% confidence
A

Type guards are expressions that narrow types in their scope. Built-in: typeof x === 'string' (primitives), x instanceof MyClass (classes), 'prop' in obj (property check), x === null (equality). Custom: functions returning x is Type (type predicates). After a type guard, TypeScript knows the narrowed type in that branch. Use type guards to handle union types safely.

99% confidence
A

Type predicates are functions returning parameterName is Type. Example: function isString(x: unknown): x is string { return typeof x === 'string'; }. When used in conditionals, TypeScript narrows the type. As of TS 4.9+, TypeScript can infer type predicates in some cases, but explicit predicates are still needed for complex logic. The predicate must be true when returned, or runtime/compile-time mismatch occurs.

99% confidence
A

Discriminated unions have a common property with literal types (the 'discriminant') unique to each member. Example: type Result = { status: 'success'; data: T } | { status: 'error'; error: Error }. Checking result.status === 'success' narrows to the success variant. Use for: messaging schemes, state management mutations, API responses, finite state machines. Always check the discriminant property.

99% confidence
A

When narrowing exhausts all union members, the remaining type is never. Use this for exhaustive switch statements: after handling all cases, the default should receive never. If you add a new union member and forget to handle it, TypeScript errors because the new type can't assign to never. Pattern: const _exhaustive: never = value; in default case throws compile error on unhandled variants.

99% confidence
A

Assertion functions throw if a condition isn't met, narrowing types in the process. Signature: function assert(condition: unknown): asserts condition. After assert(x !== null), TypeScript knows x isn't null. Can also assert specific types: asserts value is string. Unlike type predicates (return boolean), assertion functions throw on failure. Use for validation functions where failure should halt execution.

99% confidence
A

Control flow analysis (CFA) narrows types based on code structure. After if (typeof x === 'string'), TypeScript knows x is string in that branch. CFA handles: typeof checks, instanceof, truthiness, equality (===, !==), in operator, discriminated unions. TypeScript builds a control flow graph during binding, then lazily evaluates types from this graph when needed. Works backward from variable reference to root.

99% confidence
A

Type guards are expressions that narrow types in their scope. Built-in: typeof x === 'string' (primitives), x instanceof MyClass (classes), 'prop' in obj (property check), x === null (equality). Custom: functions returning x is Type (type predicates). After a type guard, TypeScript knows the narrowed type in that branch. Use type guards to handle union types safely.

99% confidence
A

Type predicates are functions returning parameterName is Type. Example: function isString(x: unknown): x is string { return typeof x === 'string'; }. When used in conditionals, TypeScript narrows the type. As of TS 4.9+, TypeScript can infer type predicates in some cases, but explicit predicates are still needed for complex logic. The predicate must be true when returned, or runtime/compile-time mismatch occurs.

99% confidence
A

Discriminated unions have a common property with literal types (the 'discriminant') unique to each member. Example: type Result = { status: 'success'; data: T } | { status: 'error'; error: Error }. Checking result.status === 'success' narrows to the success variant. Use for: messaging schemes, state management mutations, API responses, finite state machines. Always check the discriminant property.

99% confidence
A

When narrowing exhausts all union members, the remaining type is never. Use this for exhaustive switch statements: after handling all cases, the default should receive never. If you add a new union member and forget to handle it, TypeScript errors because the new type can't assign to never. Pattern: const _exhaustive: never = value; in default case throws compile error on unhandled variants.

99% confidence
A

Assertion functions throw if a condition isn't met, narrowing types in the process. Signature: function assert(condition: unknown): asserts condition. After assert(x !== null), TypeScript knows x isn't null. Can also assert specific types: asserts value is string. Unlike type predicates (return boolean), assertion functions throw on failure. Use for validation functions where failure should halt execution.

99% confidence

configuration

10 questions
A

Strict mode ("strict": true in tsconfig) enables a bundle of stricter type-checking flags: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, alwaysStrict. Recommended for all new projects. Catches potential errors at compile time. Explicitly listing flags is redundant but documents intent.

99% confidence
A

noImplicitAny prevents TypeScript from inferring any when it can't determine a type. Without it, function add(a, b) { return a + b; } silently types parameters as any. With it, TypeScript errors requiring explicit types. This eliminates hidden any 'black holes' that disable type checking. You'll only have any where explicitly declared. Essential for real type safety.

99% confidence
A

strictNullChecks makes null and undefined distinct types, not assignable to other types. Without it, string accepts null. With it, you must use string | null to allow null. Forces explicit handling of nullable values. Catches common runtime errors at compile time: 'Cannot read property of undefined'. Use optional chaining (?.) and nullish coalescing (??) for cleaner null handling.

99% confidence
A

noUncheckedIndexedAccess (TS 4.1+) adds undefined to indexed access types. With it, arr[0] is T | undefined, not just T. Forces explicit null checks when accessing array/object indices. Catches common 'undefined is not an object' errors. Example: const first = arr[0] requires if (first !== undefined) before use. More strict than strictNullChecks alone.

99% confidence
A

exactOptionalPropertyTypes (TS 4.4+) distinguishes between missing properties and properties set to undefined. With interface Config { timeout?: number }, normally both {} and { timeout: undefined } are valid. With this flag, only {} is valid - explicit undefined is disallowed. Use timeout?: number | undefined to allow explicit undefined. Catches accidental prop: undefined assignments.

99% confidence
A

Strict mode ("strict": true in tsconfig) enables a bundle of stricter type-checking flags: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, alwaysStrict. Recommended for all new projects. Catches potential errors at compile time. Explicitly listing flags is redundant but documents intent.

99% confidence
A

noImplicitAny prevents TypeScript from inferring any when it can't determine a type. Without it, function add(a, b) { return a + b; } silently types parameters as any. With it, TypeScript errors requiring explicit types. This eliminates hidden any 'black holes' that disable type checking. You'll only have any where explicitly declared. Essential for real type safety.

99% confidence
A

strictNullChecks makes null and undefined distinct types, not assignable to other types. Without it, string accepts null. With it, you must use string | null to allow null. Forces explicit handling of nullable values. Catches common runtime errors at compile time: 'Cannot read property of undefined'. Use optional chaining (?.) and nullish coalescing (??) for cleaner null handling.

99% confidence
A

noUncheckedIndexedAccess (TS 4.1+) adds undefined to indexed access types. With it, arr[0] is T | undefined, not just T. Forces explicit null checks when accessing array/object indices. Catches common 'undefined is not an object' errors. Example: const first = arr[0] requires if (first !== undefined) before use. More strict than strictNullChecks alone.

99% confidence
A

exactOptionalPropertyTypes (TS 4.4+) distinguishes between missing properties and properties set to undefined. With interface Config { timeout?: number }, normally both {} and { timeout: undefined } are valid. With this flag, only {} is valid - explicit undefined is disallowed. Use timeout?: number | undefined to allow explicit undefined. Catches accidental prop: undefined assignments.

99% confidence

operators

8 questions
A

The satisfies operator (TS 4.9+) validates a value matches a type WITHOUT changing its inferred type. const config = { port: 3000 } satisfies Config - TypeScript checks config matches Config, but infers { port: number } (not just Config). Preserves literal types and narrows inference. Compare to : Config annotation which widens to the annotation type.

99% confidence
A

Use as const satisfies Type to get immutability + type validation + narrow inference. Example: const routes = { home: '/', users: '/users' } as const satisfies Record<string, string>. The object is readonly with literal types ('/'), validated against Record, but keeps the narrow { home: '/'; users: '/users' } type instead of widening to Record. Best of all three features.

99% confidence
A

as Type (assertion): Forces a type, bypasses some checks, YOU are responsible for type safety. satisfies Type (validation): Checks compatibility, preserves inferred type, TypeScript validates. Use satisfies for: config objects, ensuring implementations match interfaces. Use as for: type casting from any/unknown, when you know more than TypeScript. satisfies is safer - prefer it when possible.

99% confidence
A

as const makes values deeply readonly with literal types. const arr = [1, 2] as const becomes readonly [1, 2] (tuple with literals) instead of number[]. Properties become readonly, strings become literal types, numbers become literal types. Use for: route definitions, configuration objects, enum alternatives. Preserves exact values for type inference.

99% confidence
A

The satisfies operator (TS 4.9+) validates a value matches a type WITHOUT changing its inferred type. const config = { port: 3000 } satisfies Config - TypeScript checks config matches Config, but infers { port: number } (not just Config). Preserves literal types and narrows inference. Compare to : Config annotation which widens to the annotation type.

99% confidence
A

Use as const satisfies Type to get immutability + type validation + narrow inference. Example: const routes = { home: '/', users: '/users' } as const satisfies Record<string, string>. The object is readonly with literal types ('/'), validated against Record, but keeps the narrow { home: '/'; users: '/users' } type instead of widening to Record. Best of all three features.

99% confidence
A

as Type (assertion): Forces a type, bypasses some checks, YOU are responsible for type safety. satisfies Type (validation): Checks compatibility, preserves inferred type, TypeScript validates. Use satisfies for: config objects, ensuring implementations match interfaces. Use as for: type casting from any/unknown, when you know more than TypeScript. satisfies is safer - prefer it when possible.

99% confidence
A

as const makes values deeply readonly with literal types. const arr = [1, 2] as const becomes readonly [1, 2] (tuple with literals) instead of number[]. Properties become readonly, strings become literal types, numbers become literal types. Use for: route definitions, configuration objects, enum alternatives. Preserves exact values for type inference.

99% confidence

advanced-types

6 questions
A

The infer keyword extracts types within conditional type extends clauses. Pattern: T extends SomeType<infer U> ? U : never. Creates an inline type variable (U) that captures the matched type. Only valid inside extends clause of conditional types. Classic example: type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any - extracts return type R from function type T.

99% confidence
A

Combine infer with template literals to extract string parts. Example: type Domain<T> = T extends \https://${string}.${infer D}` ? D : never. Given 'https://api.example.com', extracts 'com'. Can recursively parse: type Words = S extends `${infer W} ${infer R}` ? [W, ...Words] : [S]` splits 'hello world' into ['hello', 'world']. Powerful for typed routing, URL parsing, DSLs.

99% confidence
A

Mapped types transform properties of existing types. Pattern: { [K in keyof T]: NewType }. Creates new type by iterating over keys. Modifiers: readonly adds immutability, ? adds optionality, -readonly removes readonly, -? removes optional. Built-in examples: Partial<T> (all optional), Required<T> (all required), Readonly<T>. Custom: type Nullable<T> = { [K in keyof T]: T[K] | null }.

99% confidence
A

The infer keyword extracts types within conditional type extends clauses. Pattern: T extends SomeType<infer U> ? U : never. Creates an inline type variable (U) that captures the matched type. Only valid inside extends clause of conditional types. Classic example: type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any - extracts return type R from function type T.

99% confidence
A

Combine infer with template literals to extract string parts. Example: type Domain<T> = T extends \https://${string}.${infer D}` ? D : never. Given 'https://api.example.com', extracts 'com'. Can recursively parse: type Words = S extends `${infer W} ${infer R}` ? [W, ...Words] : [S]` splits 'hello world' into ['hello', 'world']. Powerful for typed routing, URL parsing, DSLs.

99% confidence
A

Mapped types transform properties of existing types. Pattern: { [K in keyof T]: NewType }. Creates new type by iterating over keys. Modifiers: readonly adds immutability, ? adds optionality, -readonly removes readonly, -? removes optional. Built-in examples: Partial<T> (all optional), Required<T> (all required), Readonly<T>. Custom: type Nullable<T> = { [K in keyof T]: T[K] | null }.

99% confidence

inference

4 questions
A

TypeScript infers types by analyzing initializers and expressions. For const x = 5, it infers x: number. Inference walks expression trees - lightweight for simple assignments, expensive for complex nested structures. TypeScript uses 'Flow Nodes' in the Control Flow Graph to track types through code. Rather than narrowing forward, TypeScript traverses backward from symbol references to determine 'flow types'. This is lazy evaluation for performance.

99% confidence
A

Contextual typing infers types from expression location rather than content. In window.onmousedown = function(e) { ... }, the parameter e is inferred as MouseEvent from the onmousedown type signature. Applies to: function call arguments, assignment right-hand sides, type assertions, object/array literal members, return statements. Contextual typing works 'the other direction' - from container to contained.

99% confidence
A

TypeScript infers types by analyzing initializers and expressions. For const x = 5, it infers x: number. Inference walks expression trees - lightweight for simple assignments, expensive for complex nested structures. TypeScript uses 'Flow Nodes' in the Control Flow Graph to track types through code. Rather than narrowing forward, TypeScript traverses backward from symbol references to determine 'flow types'. This is lazy evaluation for performance.

99% confidence
A

Contextual typing infers types from expression location rather than content. In window.onmousedown = function(e) { ... }, the parameter e is inferred as MouseEvent from the onmousedown type signature. Applies to: function call arguments, assignment right-hand sides, type assertions, object/array literal members, return statements. Contextual typing works 'the other direction' - from container to contained.

99% confidence

performance

4 questions
A

Performance benefits: (1) Return type annotations save 20-40% compile time for complex functions - cached instead of re-inferred per call, (2) Named types (interface/type) produce 50-70% smaller .d.ts files vs inline object types, (3) Explicit return types on all functions = 30-50% faster --noEmit checks, 15-25% faster incremental builds, (4) Reduces IDE memory usage 20-30% in large codebases. Critical for recursive/generic functions.

99% confidence
A

Performance debugging flags: (1) --extendedDiagnostics - shows time spent in each phase (Parse, Bind, Check, Emit), (2) --generateTrace <dir> - creates trace.json for Chrome DevTools flame graph, identifies slow types, (3) --listFiles - shows all files being compiled, catch unintended includes. For slow builds: check for deep recursion in types, complex conditional types, large union/intersection types.

99% confidence
A

Performance benefits: (1) Return type annotations save 20-40% compile time for complex functions - cached instead of re-inferred per call, (2) Named types (interface/type) produce 50-70% smaller .d.ts files vs inline object types, (3) Explicit return types on all functions = 30-50% faster --noEmit checks, 15-25% faster incremental builds, (4) Reduces IDE memory usage 20-30% in large codebases. Critical for recursive/generic functions.

99% confidence
A

Performance debugging flags: (1) --extendedDiagnostics - shows time spent in each phase (Parse, Bind, Check, Emit), (2) --generateTrace <dir> - creates trace.json for Chrome DevTools flame graph, identifies slow types, (3) --listFiles - shows all files being compiled, catch unintended includes. For slow builds: check for deep recursion in types, complex conditional types, large union/intersection types.

99% confidence

best-practices

2 questions
A

Use inference for: (1) Local variables with obvious initializers (const user = await getUser()), (2) Arrow function parameters with strong context (array.map(item => item.id)), (3) Generic arguments when obvious. Use explicit annotations for: (1) Public API functions (parameters + return types), (2) Exported functions/classes (required for .d.ts), (3) Complex return types (unions, conditionals), (4) Large codebases (>100K lines) for IDE performance.

99% confidence
A

Use inference for: (1) Local variables with obvious initializers (const user = await getUser()), (2) Arrow function parameters with strong context (array.map(item => item.id)), (3) Generic arguments when obvious. Use explicit annotations for: (1) Public API functions (parameters + return types), (2) Exported functions/classes (required for .d.ts), (3) Complex return types (unions, conditionals), (4) Large codebases (>100K lines) for IDE performance.

99% confidence

fundamentals

2 questions
A

Both define object shapes, but differ: (1) Interfaces support declaration merging (multiple declarations combine), types don't, (2) Types support unions/intersections directly (type A = B | C), interfaces use extends, (3) Interfaces show better in error messages (named vs anonymous), (4) Types can alias primitives (type ID = string), interfaces can't, (5) Performance: similar in TS 5.x, interfaces slightly faster for object shapes. Use interfaces for public APIs, types for unions/utilities.

99% confidence
A

Both define object shapes, but differ: (1) Interfaces support declaration merging (multiple declarations combine), types don't, (2) Types support unions/intersections directly (type A = B | C), interfaces use extends, (3) Interfaces show better in error messages (named vs anonymous), (4) Types can alias primitives (type ID = string), interfaces can't, (5) Performance: similar in TS 5.x, interfaces slightly faster for object shapes. Use interfaces for public APIs, types for unions/utilities.

99% confidence