TypeScript Interview Questions & Answers (2026) – RankWeb3
interface User {
id: number;
name: string;
role?: 'admin' | 'user';
}

function getUser<T extends User>(
id: number
): Promise<T> {
return fetch(`/api/users/${id}`)
.then(r => r.json());
}
// TypeScript βœ“
RankWeb3β€ΊInterview Questionsβ€ΊTypeScript
TypeScript All Levels 40 Questions Updated 2026

TypeScript Interview
Questions & Answers

πŸ“… Updated: March 2026
⏱️ Read time: ~28 min
🎯 40 questions β€” Beginner to Advanced
✍️ By RankWeb3 Team
40
Total Questions
14
Beginner
14
Intermediate
12
Advanced

🌱Beginner QuestionsQ1–Q14

1
What is TypeScript and why was it created?
BeginnerVery Common
+

TypeScript is an open-source programming language developed by Microsoft (2012) that is a strict syntactical superset of JavaScript β€” it adds optional static typing to JS. TypeScript code compiles ("transpiles") to plain JavaScript that runs in any browser or Node.js environment.

  • Static typing: Catch type errors at compile time rather than at runtime.
  • Better IDE support: Autocompletion, inline docs, and refactoring tools work far better with type information.
  • Safer refactoring: Change a function signature and the compiler instantly finds every broken call site.
  • Self-documenting code: Type signatures act as built-in documentation β€” no need to read implementation details.
  • ES features early: TypeScript supports modern JS features (decorators, enums) before they land in browsers.
πŸ’‘Key point: TypeScript is a superset β€” all valid JavaScript is valid TypeScript. You can adopt it gradually, adding types file by file.
2
What are TypeScript's basic types?
BeginnerVery Common
+
TypeScript
// Primitive types: let name: string = 'Meraj'; let age: number = 25; let active: boolean = true; let nothing: null = null; let undef: undefined = undefined; let id: symbol = Symbol('id'); let big: bigint = 9007199254740991n; // Array types: let nums: number[] = [1, 2, 3]; let strs: Array<string> = ['a', 'b']; // Tuple β€” fixed-length array with known types: let pair: [string, number] = ['Meraj', 25]; // any β€” opt out of type checking (avoid!): let value: any = 'could be anything'; value = 42; // no error // unknown β€” safer alternative to any: let input: unknown = getInput(); if (typeof input === 'string') input.toUpperCase(); // must check first // void β€” function returns nothing: function log(msg: string): void { console.log(msg); } // never β€” function never returns (throws or infinite loop): function fail(msg: string): never { throw new Error(msg); }
3
What is the difference between interface and type in TypeScript?
BeginnerVery Common
+
Featureinterfacetype
Object shapesβœ… Primary use caseβœ… Yes
Declaration mergingβœ… Yes (open)❌ No
Extend/Implementextends / implements& intersection
Union types❌ Noβœ… Yes
Primitive aliases❌ Noβœ… Yes
Tuple types❌ Noβœ… Yes
Computed keys❌ Limitedβœ… Yes (mapped types)
TypeScript
// Interface β€” open, extendable, use for objects/classes: interface Animal { name: string } interface Animal { sound: string } // merges! now has name + sound interface Dog extends Animal { breed: string } // type β€” closed, versatile, use for unions/intersections: type ID = string | number; // union type Point = { x: number; y: number }; type Named = Point & { name: string }; // intersection type Pair = [string, number]; // tuple
πŸ’‘Rule of thumb: Use interface for object shapes that may need extending or implementing. Use type for unions, intersections, tuples, and primitives. For most object types, either works β€” pick one convention and stick to it.
4
What is type inference in TypeScript?
BeginnerVery Common
+

TypeScript can automatically infer the type of a variable from its initial value β€” you don't need to explicitly annotate every type. This keeps code clean without sacrificing safety.

TypeScript
// TypeScript infers these types automatically: let name = 'Meraj'; // inferred: string let age = 25; // inferred: number let nums = [1, 2, 3]; // inferred: number[] let obj = { x: 1, y: 'a' }; // inferred: { x: number; y: string } // Return type inferred from function body: function add(a: number, b: number) { return a + b; // return type inferred as number } // Contextual typing β€” inferred from context: const names = ['Alice', 'Bob']; names.forEach(n => n.toUpperCase()); // n inferred as string // When to annotate explicitly: // 1. When inference isn't specific enough let id: string | number; // can't infer from no initial value // 2. Function parameters β€” always annotate function greet(name: string): string { return `Hi ${name}`; }
5
What are union and intersection types?
BeginnerVery Common
+
TypeScript
// Union ( | ) β€” value can be ONE of several types: type ID = string | number; function printId(id: ID) { if (typeof id === 'string') { console.log(id.toUpperCase()); // string methods available } else { console.log(id.toFixed(2)); // number methods available } } type Status = 'pending' | 'active' | 'cancelled'; // string literal union // Intersection ( & ) β€” value must satisfy ALL types: type Employee = { name: string; dept: string }; type Manager = { reports: number }; type Senior = Employee & Manager; // Must have: name, dept, AND reports const vp: Senior = { name: 'Alice', dept: 'Eng', reports: 12 };
6
What are optional and readonly properties?
BeginnerCommon
+
TypeScript
interface User { id: number; // required name: string; // required email?: string; // optional (string | undefined) readonly createdAt: Date; // readonly β€” cannot be changed after creation } const user: User = { id: 1, name: 'Meraj', createdAt: new Date() }; // user.createdAt = new Date(); // ❌ Cannot assign to readonly user.email = 'me@x.com'; // βœ… optional can be set later // Readonly array: const nums: readonly number[] = [1, 2, 3]; // nums.push(4); // ❌ Cannot mutate readonly array // Optional function parameters: function greet(name: string, greeting?: string): string { return `${greeting ?? 'Hello'}, ${name}`; } greet('Meraj'); // "Hello, Meraj" greet('Meraj', 'Hi'); // "Hi, Meraj"
7
What are enums in TypeScript?
BeginnerCommon
+
TypeScript
// Numeric enum (default β€” values auto-increment from 0): enum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3 } let dir: Direction = Direction.Up; // 0 let name = Direction[0]; // 'Up' (reverse mapping) // String enum (preferred β€” explicit values, no reverse mapping): enum Status { Pending = 'PENDING', Active = 'ACTIVE', Cancelled = 'CANCELLED', } // Const enum β€” inlined at compile time (no runtime object): const enum Color { Red, Green, Blue } let c = Color.Red; // compiled to: let c = 0; // Alternative: string literal union (often preferred over enums): type Direction2 = 'up' | 'down' | 'left' | 'right';
⚠️Many TypeScript teams prefer string literal unions over enums β€” they're simpler, have no runtime overhead, and work better with JSON APIs. Use const enum if you do use enums to avoid generating a runtime object.
8
What is the difference between any, unknown, and never?
BeginnerVery Common
+
TypeAssignable fromAssignable toOperations
anyEverythingEverythingAll allowed β€” disables type checking
unknownEverythingOnly any/unknownMust narrow type first
neverNothingEverythingNo operations β€” type that never exists
TypeScript
// any β€” escape hatch, avoid using: let a: any = 'hello'; a.toFixed(); // no error β€” even though string has no toFixed! // unknown β€” safe alternative to any: let u: unknown = fetch('/api'); // u.json(); // ❌ Error: must narrow first if (u instanceof Response) u.json(); // βœ… // never β€” use in exhaustive checks: type Shape = 'circle' | 'square'; function area(shape: Shape) { switch (shape) { case 'circle': return Math.PI; case 'square': return 1; default: const _: never = shape; // compile error if new shape isn't handled } }
9
What is type assertion (as) in TypeScript?
BeginnerCommon
+

Type assertions tell the TypeScript compiler "trust me, I know this value's type better than you do." They are checked at compile time but have no runtime effect β€” no actual conversion happens.

TypeScript
// as syntax (preferred): const input = document.getElementById('email') as HTMLInputElement; console.log(input.value); // TypeScript knows it's HTMLInputElement // Angle bracket syntax (legacy, can't use in JSX): const input2 = <HTMLInputElement>document.getElementById('email'); // Non-null assertion (!): const el = document.getElementById('app')!; // assert not null el.innerHTML = 'Hello'; // const assertion β€” makes values readonly and literal: const config = { endpoint: '/api', retries: 3, } as const; // config is: { readonly endpoint: '/api'; readonly retries: 3 } // config.retries = 5; // ❌ Cannot assign to readonly
⚠️Use type assertions sparingly. They override the type checker β€” if you assert wrong, you'll get runtime errors TypeScript could have prevented. Prefer type narrowing (typeof, instanceof) wherever possible.
10
What is tsconfig.json and what are the key compiler options?
BeginnerVery Common
+
JSON β€” tsconfig.json
{ "compilerOptions": { // --- Output --- "target": "ES2022", // JS version to compile to "module": "CommonJS", // module system (CommonJS / ESNext) "outDir": "./dist", // output directory "rootDir": "./src", // source directory // --- Strictness (always enable!) --- "strict": true, // enables all strict checks below "noImplicitAny": true, // error when type inferred as any "strictNullChecks": true, // null/undefined not assignable by default // --- Modules --- "esModuleInterop": true, // allow default import from CJS modules "moduleResolution": "node", // --- Quality --- "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "forceConsistentCasingInFileNames": true, // --- Source Maps --- "sourceMap": true, // for debugging "declaration": true // generate .d.ts files }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }
11
What are TypeScript classes and access modifiers?
BeginnerCommon
+
TypeScript
class BankAccount { public owner: string; // accessible everywhere (default) private balance: number; // only this class protected type: string; // this class + subclasses readonly id: string; // cannot be reassigned // Parameter shorthand β€” creates + assigns in one line: constructor(public name: string, private pin: number) { this.owner = name; this.balance = 0; this.type = 'savings'; this.id = crypto.randomUUID(); } get currentBalance(): number { return this.balance; } deposit(amount: number): void { if (amount <= 0) throw new Error('Positive amount only'); this.balance += amount; } static create(name: string, pin: number): BankAccount { return new BankAccount(name, pin); } } // Abstract class β€” cannot be instantiated directly: abstract class Shape { abstract area(): number; describe() { return `Area: ${this.area()}`; } }
12
What is strictNullChecks and why is it important?
BeginnerVery Common
+

With strictNullChecks enabled (strongly recommended), null and undefined are not automatically assignable to every type. You must explicitly handle them β€” preventing the dreaded "Cannot read properties of null" runtime error.

TypeScript
// Without strictNullChecks (dangerous): let name: string = null; // allowed! potential runtime crash // With strictNullChecks (safe): let name: string = null; // ❌ Error: null not assignable to string let name: string | null = null; // βœ… explicit union // Must narrow before use: function getLength(s: string | null): number { if (s === null) return 0; return s.length; // TypeScript knows s is string here } // Optional chaining (?.) β€” safe access: const len = user?.address?.city?.length; // undefined if any is null/undefined // Nullish coalescing (??) β€” default for null/undefined: const display = user.name ?? 'Anonymous';
13
What are function overloads in TypeScript?
Beginner
+

Function overloads let you define multiple type signatures for one function β€” useful when a function behaves differently based on argument types, but the actual implementation is a single function.

TypeScript
// Overload signatures (no body): function format(value: string): string; function format(value: number, decimals: number): string; function format(value: Date): string; // Implementation signature (must cover all overloads): function format(value: string | number | Date, decimals?: number): string { if (typeof value === 'string') return value.trim(); if (typeof value === 'number') return value.toFixed(decimals ?? 2); if (value instanceof Date) return value.toISOString(); throw new Error('Unknown type'); } format(' hello '); // 'hello' format(3.14159, 2); // '3.14' format(new Date()); // '2026-03-23T...'
14
What are declaration files (.d.ts)?
BeginnerCommon
+

Declaration files (.d.ts) contain only type information β€” no runtime code. They describe the shape of JavaScript libraries so TypeScript can type-check code that uses them.

TypeScript
// Install type definitions for a JS library: // npm install --save-dev @types/lodash // npm install --save-dev @types/node // Custom .d.ts for a plain JS module: // my-library.d.ts declare module 'my-library' { export function greet(name: string): string; export const version: string; } // Augment global types: declare global { interface Window { analytics: { track(event: string): void }; } } // When tsc --declaration is set, it auto-generates .d.ts // from your TypeScript source β€” for publishing npm packages

⚑Intermediate QuestionsQ15–Q28

15
What are generics in TypeScript?
IntermediateVery Common
+

Generics allow you to write reusable, type-safe code that works with any type, without sacrificing type information. They're like type parameters β€” placeholders filled in when you use the function or class.

TypeScript
// Without generics β€” loses type info: function first(arr: any[]): any { return arr[0]; } const n = first([1, 2, 3]); // n is any, not number // With generics β€” preserves type: function first<T>(arr: T[]): T { return arr[0]; } const n = first([1, 2, 3]); // n is number βœ“ const s = first(['a', 'b']); // s is string βœ“ // Generic interface: interface ApiResponse<T> { data: T; status: number; message: string; } type UserResponse = ApiResponse<User>; // Generic with constraint: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const name = getProperty({ name: 'Meraj', age: 25 }, 'name'); // string // Multiple type params: function zip<A, B>(a: A[], b: B[]): [A, B][] { return a.map((item, i) => [item, b[i]]); }
16
What are TypeScript utility types?
IntermediateVery Common
+
TypeScript β€” Built-in Utility Types
interface User { id: number; name: string; email: string; role: string } // Partial β€” all properties optional: type UpdateUser = Partial<User>; // Required β€” all properties required: type FullUser = Required<User>; // Readonly β€” all properties readonly: type FrozenUser = Readonly<User>; // Pick β€” select specific properties: type UserPreview = Pick<User, 'id' | 'name'>; // Omit β€” exclude specific properties: type PublicUser = Omit<User, 'email' | 'role'>; // Record β€” key-value map type: type UserMap = Record<string, User>; // Exclude β€” remove union members: type NonString = Exclude<string | number | boolean, string>; // β†’ number | boolean // Extract β€” keep matching union members: type Strings = Extract<string | number | boolean, string | boolean>; // β†’ string | boolean // NonNullable β€” remove null and undefined: type Name = NonNullable<string | null | undefined>; // string // ReturnType β€” extract return type of function: type Greet = () => string; type R = ReturnType<Greet>; // string // Parameters β€” extract parameter types as tuple: type P = Parameters<typeof fetch>; // [input: RequestInfo, init?: RequestInit] // Awaited β€” unwrap Promise type: type Data = Awaited<Promise<User>>; // User
17
What is type narrowing in TypeScript?
IntermediateVery Common
+

Type narrowing is the process of refining a type within a conditional block based on a runtime check. TypeScript's control flow analysis tracks which types are possible at each point in the code.

TypeScript β€” Narrowing Guards
// typeof narrowing: function process(val: string | number) { if (typeof val === 'string') { val.toUpperCase(); // string here } else { val.toFixed(); // number here } } // instanceof narrowing: if (error instanceof TypeError) { error.message } // in narrowing β€” check if property exists: if ('fly' in animal) { animal.fly(); } // Equality narrowing: if (val === null) { /* handle null */ } // Type predicate (user-defined type guard): function isUser(val: unknown): val is User { return (typeof val === 'object' && val !== null && 'id' in val && 'name' in val); } if (isUser(data)) { data.name; } // TypeScript knows data is User // Discriminated union narrowing: type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; side: number } | { kind: 'triangle'; base: number; height: number }; function area(s: Shape) { switch (s.kind) { case 'circle': return Math.PI * s.radius ** 2; case 'square': return s.side ** 2; case 'triangle': return 0.5 * s.base * s.height; } }
18
What are mapped types in TypeScript?
IntermediateCommon
+

Mapped types transform the properties of an existing type by iterating over its keys. They're the foundation of many utility types like Partial, Readonly, and Pick.

TypeScript
type User = { id: number; name: string; email: string }; // Make all properties optional (same as Partial<T>): type MyPartial<T> = { [K in keyof T]?: T[K] }; // Make all properties nullable: type Nullable<T> = { [K in keyof T]: T[K] | null }; // Transform values to getters: type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] }; // User β†’ { getId: () => number; getName: () => string; ... } // Filter out certain keys: type OnlyStrings<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] }; // OnlyStrings<User> β†’ { name: string; email: string }
19
What are conditional types in TypeScript?
IntermediateCommon
+

Conditional types select one of two types based on a condition β€” following the pattern: T extends U ? X : Y. They're the type-level equivalent of ternary expressions.

TypeScript
// Basic conditional type: type IsString<T> = T extends string ? 'yes' : 'no'; type A = IsString<string>; // 'yes' type B = IsString<number>; // 'no' // Distributive β€” distributes over union members: type NonArray<T> = T extends any[] ? never : T; type R = NonArray<string | number[] | boolean>; // string | boolean // infer β€” extract type from within another type: type UnpackArray<T> = T extends (infer U)[] ? U : T; type N = UnpackArray<number[]>; // number type S = UnpackArray<string>; // string (not array) // How ReturnType is implemented: type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
20
What are template literal types?
IntermediateCommon
+

Template literal types (TypeScript 4.1+) build new string types by combining literal types using template literal syntax β€” like tagged template literals but at the type level.

TypeScript
type EventName = 'click' | 'focus' | 'blur'; type Handler = `on${Capitalize<EventName>}`; // β†’ 'onClick' | 'onFocus' | 'onBlur' type Direction = 'top' | 'right' | 'bottom' | 'left'; type CSSMargin = `margin-${Direction}`; // β†’ 'margin-top' | 'margin-right' | 'margin-bottom' | 'margin-left' // Real-world: typed event emitter type Events = { userCreated: User; orderPlaced: Order }; type EventKey = keyof Events; type EventValue<K extends EventKey> = Events[K]; // Built-in string manipulation types: Uppercase<'hello'> // 'HELLO' Lowercase<'WORLD'> // 'world' Capitalize<'hello'> // 'Hello' Uncapitalize<'Hello'> // 'hello'
21
What are index signatures and index access types?
Intermediate
+
TypeScript
// Index signature β€” dynamic keys of known type: interface Dictionary<V> { [key: string]: V; } const scores: Dictionary<number> = { alice: 95, bob: 87 }; // Both named properties and index signature: interface Config { debug: boolean; // named property [key: string]: unknown; // index sig (named props must match value type) } // Index access types β€” look up property type: type User = { id: number; name: string; address: { city: string } }; type IdType = User['id']; // number type City = User['address']['city']; // string type AllVals = User[keyof User]; // number | string | {city: string} // Array element type: type Arr = string[]; type Item = Arr[number]; // string
22
What are decorators in TypeScript?
IntermediateCommon
+

Decorators are a special kind of declaration that can be attached to classes, methods, properties, or parameters. They are functions that receive the target they decorate and can modify or annotate it. Used heavily in Angular, NestJS, and TypeORM.

TypeScript β€” Decorators (experimental)
// tsconfig: "experimentalDecorators": true // Class decorator: function Sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @Sealed class Config { api = '/api'; } // Method decorator: function log(target: any, key: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${key} with`, args); return original.apply(this, args); }; return descriptor; } class Service { @log greet(name: string) { return `Hi ${name}`; } } // NestJS / Angular pattern: @Injectable() class UserService { @Get('/users/:id') async getUser(@Param('id') id: string) { ... } }
23
What are keyof and typeof operators in TypeScript?
IntermediateVery Common
+
TypeScript
// keyof β€” creates a union of all keys of a type: type User = { id: number; name: string; email: string }; type UserKeys = keyof User; // 'id' | 'name' | 'email' // Safe property accessor using keyof: function get<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } get(user, 'name'); // βœ… string get(user, 'invalid'); // ❌ compile error // typeof β€” get the type of a value (at type level): const config = { host: 'localhost', port: 3000 }; type Config = typeof config; // β†’ { host: string; port: number } // typeof on a function: function add(a: number, b: number) { return a + b; } type Add = typeof add; // (a: number, b: number) => number // Combine keyof + typeof: type ConfigKeys = keyof typeof config; // 'host' | 'port'
24
How do you type asynchronous functions and Promises?
IntermediateVery Common
+
TypeScript
interface User { id: number; name: string } // async function return type is always Promise<T>: async function fetchUser(id: number): Promise<User> { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error('Fetch failed'); return res.json() as Promise<User>; } // Error handling with typed errors: async function safeGet<T>(url: string): Promise<[T | null, Error | null]> { try { const data = await (await fetch(url)).json() as T; return [data, null]; } catch (err) { return [null, err instanceof Error ? err : new Error('Unknown')]; } } // Typing Promise.all: const [user, posts] = await Promise.all([ fetchUser(1), // Promise<User> fetchPosts(1), // Promise<Post[]> ]); // user: User, posts: Post[] β€” both fully typed βœ“
25
What is the difference between structural and nominal typing?
IntermediateCommon
+

TypeScript uses structural typing (duck typing) β€” if two types have the same shape, they're compatible, regardless of their name. This differs from languages like Java/C# which use nominal typing (types must explicitly declare compatibility).

TypeScript
// Structural typing β€” compatible because same shape: interface Point { x: number; y: number } interface Vector { x: number; y: number } function draw(p: Point) {} const v: Vector = { x: 1, y: 2 }; draw(v); // βœ… works! Point and Vector have same structure // Extra properties are fine when assigned to wider type: const p = { x: 1, y: 2, z: 3 }; draw(p); // βœ… extra property z is ignored // But NOT when using object literal directly (excess property check): draw({ x: 1, y: 2, z: 3 }); // ❌ Object literal excess property check // Simulate nominal typing with branded types: type UserId = number & { readonly _brand: 'UserId' }; type OrderId = number & { readonly _brand: 'OrderId' }; function getUser(id: UserId) {} getUser(1 as OrderId); // ❌ Type error β€” prevents mix-up!
26
How do you use TypeScript with React?
IntermediateVery Common
+
TypeScript + React (.tsx)
import { useState, useEffect, FC } from 'react'; // Typing props with interface: interface UserCardProps { user: User; onSelect: (id: number) => void; isActive?: boolean; children?: React.ReactNode; } // Functional component with typed props: const UserCard: FC<UserCardProps> = ({ user, onSelect, isActive = false }) => { return <div onClick={() => onSelect(user.id)}>{user.name}</div>; }; // Typing useState: const [user, setUser] = useState<User | null>(null); const [count, setCount] = useState(0); // inferred: number // Typing useRef: const inputRef = useRef<HTMLInputElement>(null); // Typing events: const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); }; const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {}; // Typing children patterns: interface LayoutProps { children: React.ReactNode; // any renderable content } interface Wrapper { children: React.ReactElement; // single React element only }
27
What is the satisfies operator in TypeScript?
Intermediate
+

The satisfies operator (TypeScript 4.9+) validates that an expression matches a type without widening its type β€” you get type checking AND preserve the literal/specific type for use later.

TypeScript
type Palette = Record<string, string | [number, number, number]>; // ❌ type annotation β€” loses specific type info: const colors: Palette = { red: [255, 0, 0], green: '#00ff00', }; colors.red; // type: string | [number, number, number] β€” lost specificity // βœ… satisfies β€” validates AND preserves specific type: const colors2 = { red: [255, 0, 0], green: '#00ff00', } satisfies Palette; colors2.red; // type: [number, number, number] βœ“ (not widened) colors2.green; // type: string βœ“ (not widened) colors2.red.map(...); // array methods available!
28
What is module augmentation in TypeScript?
Intermediate
+

Module augmentation lets you add types to existing modules β€” either third-party libraries or built-in TypeScript types β€” without modifying the original source.

TypeScript
// Extend Express Request to add custom user property: // types/express.d.ts import { User } from './models'; declare module 'express-serve-static-core' { interface Request { user?: User; // now req.user is typed! requestId: string; } } // Extend built-in Array type: declare global { interface Array<T> { last(): T | undefined; } } Array.prototype.last = function() { return this[this.length - 1]; };

πŸ”₯Advanced QuestionsQ29–Q40

29
What are higher-kinded types and how do you simulate them in TypeScript?
Advanced
+

TypeScript doesn't directly support higher-kinded types (type constructors as generic parameters), but they can be simulated using interface maps β€” a technique called "defunctionalization" or "type-level encoding".

TypeScript β€” Simulating HKTs
// We want: map<F>(fn: (a: A) => B, fa: F<A>): F<B> // But TypeScript can't have F<A> as a type param directly // Simulate with type registry (defunctionalization): interface URItoKind<A> { 'Array': A[]; 'Option': Option<A>; 'Promise': Promise<A>; } type URIS = keyof URItoKind<any>; type Kind<URI extends URIS, A> = URItoKind<A>[URI]; interface Functor<URI extends URIS> { map<A, B>(fa: Kind<URI, A>, f: (a: A) => B): Kind<URI, B>; } // Now you can write: Functor<'Array'> | Functor<'Promise'>
πŸ’‘Real-world: This pattern is used in fp-ts, a functional programming library for TypeScript, to implement type classes like Functor, Applicative, and Monad in a type-safe way.
30
What are recursive types in TypeScript?
AdvancedCommon
+
TypeScript β€” Recursive Types
// JSON value (self-referential): type Json = | string | number | boolean | null | Json[] | { [key: string]: Json }; // Tree structure: type TreeNode<T> = { value: T; children: TreeNode<T>[]; }; // Recursive array flattening type (TS 4.1+): type DeepReadonly<T> = T extends (infer U)[] ? ReadonlyArray<DeepReadonly<U>> : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T; // Deep partial: type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
31
What are variance annotations in TypeScript?
Advanced
+

Variance describes how a generic type's subtype relationships relate to its type argument's subtype relationships. TypeScript 4.7 added explicit variance annotations (in/out) for performance and clarity.

TypeScript
// Covariant (out) β€” safe to read, preserves subtype: interface Producer<out T> { produce(): T; // T only appears in output position } // Producer<Dog> is assignable to Producer<Animal> // Contravariant (in) β€” safe to write, reverses subtype: interface Consumer<in T> { consume(t: T): void; // T only appears in input position } // Consumer<Animal> is assignable to Consumer<Dog> // Invariant (in out) β€” neither assignable: interface ReadWrite<in out T> { read(): T; write(t: T): void; }
32
What is the infer keyword and how do you use it?
AdvancedCommon
+
TypeScript β€” infer Patterns
// infer captures a type within a conditional type: // "If T matches this pattern, infer the part we call U" // Extract promise value: type Unwrap<T> = T extends Promise<infer U> ? U : T; type A = Unwrap<Promise<string>>; // string // Extract first argument of a function: type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never; type B = FirstArg<(n: number, s: string) => void>; // number // Extract element type from array: type ElementType<T> = T extends (infer E)[] ? E : never; type C = ElementType<User[]>; // User // Extract constructor parameter types: type ConstructorParams<T> = T extends new (...args: infer P) => any ? P : never;
33
What are tuple types and variadic tuple types?
Advanced
+
TypeScript
// Basic tuple β€” fixed length, typed positions: type Point = [number, number]; type Entry = [string, number]; type Labelled = [label: string, value: number]; // named elements // Optional tuple elements: type Config = [host: string, port?: number]; // Rest elements in tuples: type Head = [string, ...number[]]; // string followed by any numbers type Tail = [boolean, ...string[], number]; // TS 4.0+ // Variadic tuple types (TS 4.0) β€” spread tuples together: type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]; type Result = Concat<[string, number], [boolean, null]>; // β†’ [string, number, boolean, null] // Type-safe curry / partial application: type Curry<F extends (...args: any[]) => any> = F extends (first: infer A, ...rest: infer R) => infer Ret ? (first: A) => R extends [] ? Ret : Curry<(...rest: R) => Ret> : F;
34
What are type-safe builder patterns in TypeScript?
Advanced
+

Type-safe builders use TypeScript's type system to ensure methods must be called in the correct order and required fields are set before the object can be built.

TypeScript β€” Builder Pattern
// Each method returns a new type that reflects what's been set: class QueryBuilder<T extends Partial<{ table: string; fields: string[] }>> { private state = {} as T; from<N extends string>(name: N) { return new QueryBuilder<T & { table: N }>(); } select(...fields: string[]) { return new QueryBuilder<T & { fields: string[] }>(); } // build() only available when both table AND fields are set: build(this: QueryBuilder<{ table: string; fields: string[] }>): string { return `SELECT ${this.state.fields.join(', ')} FROM ${this.state.table}`; } } const q = new QueryBuilder() .from('users') .select('id', 'name') .build(); // βœ… only works because both .from() and .select() were called
35
What are branded/opaque types and why use them?
AdvancedCommon
+

Branded types (also called opaque types) add a compile-time "tag" to primitive types to make distinct concepts incompatible with each other β€” even if their underlying type is the same.

TypeScript β€” Branded Types
// Problem: these are all just 'number' β€” easy to mix up! function transfer(fromId: number, toId: number, amount: number) {} transfer(amount, toId, fromId); // ❌ no error! wrong arg order // Branded type solution: declare const __brand: unique symbol; type Brand<T, B> = T & { readonly [__brand]: B }; type UserId = Brand<number, 'UserId'>; type Amount = Brand<number, 'Amount'>; // Constructor functions (only place to create branded values): const UserId = (id: number) => id as UserId; const Amount = (n: number) => n as Amount; function transfer(fromId: UserId, toId: UserId, amount: Amount) {} transfer(UserId(1), UserId(2), Amount(500)); // βœ… transfer(Amount(500), UserId(2), UserId(1)); // ❌ compile error!
36
What is the TypeScript project references feature?
Advanced
+

Project references allow splitting a large TypeScript codebase into multiple independent sub-projects that can be compiled separately β€” improving build times and enforcing module boundaries in monorepos.

JSON β€” tsconfig (Monorepo)
// packages/core/tsconfig.json { "compilerOptions": { "composite": true, // required for project references "outDir": "./dist" } } // packages/app/tsconfig.json { "compilerOptions": { "outDir": "./dist" }, "references": [ { "path": "../core" } // depends on core ] } // Build only changed projects: // tsc --build (or tsc -b) // tsc -b --watch (incremental watch mode) // Benefits: // - Incremental builds (only recompile what changed) // - Enforces package boundaries (can't accidentally import internals) // - Editor builds are much faster
37
What is the using keyword and explicit resource management?
Advanced
+

TypeScript 5.2 introduced using and await using β€” implementing the TC39 Explicit Resource Management proposal. Objects that implement [Symbol.dispose] are automatically cleaned up when the block exits.

TypeScript 5.2+ β€” using keyword
// Implement Disposable interface: class DatabaseConnection { constructor(private conn: Connection) {} query(sql: string) { return this.conn.execute(sql); } // Automatically called when 'using' block exits: [Symbol.dispose]() { this.conn.close(); console.log('Connection closed βœ“'); } } // using β€” synchronous cleanup: { using db = new DatabaseConnection(openConn()); await db.query('SELECT ...'); } // db[Symbol.dispose]() called automatically here! // Even if an exception was thrown! // await using β€” async cleanup: class AsyncResource { async [Symbol.asyncDispose]() { await this.flushAndClose(); } } { await using resource = new AsyncResource(); } // awaits Symbol.asyncDispose on exit
38
How do you design type-safe APIs in TypeScript?
AdvancedCommon
+
TypeScript β€” Type-safe API Patterns
// 1. Zod for runtime validation + type inference: import { z } from 'zod'; const UserSchema = z.object({ id: z.number(), name: z.string().min(1), email: z.string().email(), }); type User = z.infer<typeof UserSchema>; // derived from schema // 2. tRPC β€” full end-to-end type safety: // Server defines procedure, client gets full type inference const router = t.router({ getUser: t.procedure .input(z.object({ id: z.number() })) .query(async ({ input }) => UserSchema.parse(await db.findUser(input.id))) }); // 3. Result type β€” explicit error handling (no thrown exceptions): type Result<T, E = Error> = | { ok: true; value: T } | { ok: false; error: E }; async function getUser(id: number): Promise<Result<User>> { try { const user = await db.find(id); return { ok: true, value: user }; } catch (e) { return { ok: false, error: e as Error }; } }
39
What performance pitfalls should you avoid in TypeScript?
Advanced
+
TypeScript β€” Performance Pitfalls
// 1. Large union types are slow to check: // ❌ Avoid: 1000-member union type Slow = 'a' | 'b' | 'c' | ... | 'zzz'; // βœ… Use: index type instead const keys = ['a', 'b', 'c'] as const; type Fast = typeof keys[number]; // 2. Deep recursive types hit recursion limits: // ❌ Avoid deep recursion without base case // βœ… Add depth limit: type DeepReadonly<T, D extends number[] = []> = D['length'] extends 5 ? T // stop at depth 5 : { readonly [K in keyof T]: DeepReadonly<T[K], [...D, 0]> }; // 3. Avoid barrel files (index.ts re-exporting everything): // They force TS to load ALL types even when only one is needed // Use path imports: import { User } from './models/User' // 4. incremental compilation (tsconfig): { "incremental": true, "tsBuildInfoFile": "./.tsbuildinfo" } // 5. Use 'skipLibCheck': true to skip .d.ts type checking // (validates your code, not node_modules)
40
What are the major differences between TypeScript 4.x and 5.x?
AdvancedCommon
+
  • TS 5.0: Decorators (new standard β€” aligned with TC39 proposal), const type parameters (<const T> always infers literal types), verbatimModuleSyntax flag, bundle size reduction (10–25% smaller tsc).
  • TS 5.1: Unrelated types for getter/setter, undefined as explicit return type, JSX transform improvements.
  • TS 5.2: using / await using (Explicit Resource Management), decorator metadata, @types/node improvements.
  • TS 5.3: Import attributes, resolution-mode for imports, narrowing improvements for switch(true).
  • TS 5.4: Preserved narrowing in closures after last assignment, NoInfer<T> utility type.
  • TS 5.5: Inferred type predicates (functions that return booleans auto-infer type predicates), --isolatedDeclarations flag for parallel declaration emit.
TypeScript 5.x highlights
// TS 5.0: const type parameter β€” infer literals: function first<const T extends readonly unknown[]>(arr: T): T[0] { return arr[0]; } const x = first([1, 'a', true]); // type: 1 (literal), not number // TS 5.4: NoInfer β€” prevent T from being inferred from a param: function createState<T>(initial: T, fallback: NoInfer<T>): T { return initial ?? fallback; } createState('hello', 42); // ❌ fallback must match T (string) now // TS 5.5: Inferred type predicates: const isString = (x: unknown) => typeof x === 'string'; // TS 5.5 automatically infers: (x: unknown) => x is string [1, 'a', null].filter(isString); // type: string[] β€” no cast needed!

Up Next: System Design

You've covered TypeScript. Next in the expanded series is System Design β€” scalability, load balancing, caching, databases, and real-world architecture patterns.

← SQL & Databases Q&A