TypeScript Cheatsheet
Generics
Use this TypeScript reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Generic Functions
// Single type parameter function identity<T>(value: T): T { return value; } identity<string>("hello"); // explicit identity(42); // inferred: T = number // Multiple type parameters function pair<A, B>(a: A, b: B): [A, B] { return [a, b]; } pair("hello", 42); // [string, number] // Generic arrow function (in .tsx, add trailing comma to disambiguate from JSX) const wrap = <T,>(val: T): { value: T } => ({ value: val }); // Return type inferred from parameter function first<T>(arr: T[]): T | undefined { return arr[0]; }
Generic Constraints
// extends constrains T to a shape function getLength<T extends { length: number }>(arg: T): number { return arg.length; } getLength("hello"); // OK getLength([1, 2, 3]); // OK getLength(42); // Error: number has no 'length' // keyof constraint — key must be a property of T function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { name: "Alice", age: 30 }; getProperty(user, "name"); // string getProperty(user, "age"); // number // Multiple constraints via intersection function processItem<T extends Serializable & Loggable>(item: T): void { item.serialize(); item.log(); } // Constrain to primitive or specific types function toArray<T extends string | number | boolean>(val: T): T[] { return [val]; }
Default Type Parameters
// Default applied when type is not inferred or provided interface Container<T = string> { value: T; } const c1: Container = { value: "hello" }; // T = string const c2: Container<number> = { value: 42 }; // T = number // Default with constraint function createList<T extends object = Record<string, unknown>>(): T[] { return []; } // Multiple defaults type ApiResponse<T = unknown, E = Error> = | { ok: true; data: T } | { ok: false; error: E };
Generic Interfaces
interface Repository<T> { findById(id: number): Promise<T | null>; findAll(): Promise<T[]>; save(entity: T): Promise<T>; delete(id: number): Promise<void>; } interface Pair<A, B> { first: A; second: B; swap(): Pair<B, A>; } // Generic method inside non-generic interface interface Converter { convert<T, U>(value: T, fn: (v: T) => U): U; }
Generic Classes
class Stack<T> { private items: T[] = []; push(item: T): this { this.items.push(item); return this; } pop(): T | undefined { return this.items.pop(); } peek(): T | undefined { return this.items.at(-1); } isEmpty(): boolean { return this.items.length === 0; } get size(): number { return this.items.length; } toArray(): T[] { return [...this.items]; } } const stack = new Stack<number>(); stack.push(1).push(2).push(3); stack.peek(); // 3 stack.pop(); // 3 // Generic class with constraint class SortedList<T extends { compareTo(other: T): number }> { private items: T[] = []; add(item: T): void { this.items.push(item); this.items.sort((a, b) => a.compareTo(b)); } getAll(): T[] { return [...this.items]; } }
Generic Type Aliases
// Simple alias type Nullable<T> = T | null; type Optional<T> = T | undefined; type Maybe<T> = T | null | undefined; // Higher-order generic type Mapper<T, U> = (value: T, index: number, arr: T[]) => U; type Predicate<T> = (value: T) => boolean; type Reducer<T, R> = (acc: R, cur: T) => R; // Result / Either type type Result<T, E = Error> = | { ok: true; value: T } | { ok: false; error: E }; // Record-like with constraint type Indexed<T, K extends string | number | symbol = string> = Record<K, T>; // Recursive generic type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]; }; type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]; };
Conditional Types with Generics
// Basic conditional type IsArray<T> = T extends any[] ? true : false; type A = IsArray<string[]>; // true type B = IsArray<number>; // false // Unwrap array element type type ElementOf<T> = T extends (infer E)[] ? E : never; type E = ElementOf<string[]>; // string // Flatten union of arrays type Flatten<T> = T extends Array<infer U> ? U : T; type F = Flatten<number[] | string>; // number | string // NonNullable implementation type MyNonNullable<T> = T extends null | undefined ? never : T; // Unwrap Promise type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T; type Aw = Awaited<Promise<Promise<string>>>; // string // Distribute over union type ToArray<T> = T extends any ? T[] : never; type TA = ToArray<string | number>; // string[] | number[] // Prevent distribution with [] type ToArrayStrict<T> = [T] extends [any] ? T[] : never; type TAS = ToArrayStrict<string | number>; // (string | number)[]
Mapped Types with Generics
// Pick implementation type MyPick<T, K extends keyof T> = { [P in K]: T[P]; }; // Omit implementation type MyOmit<T, K extends keyof T> = MyPick<T, Exclude<keyof T, K>>; // Partial implementation type MyPartial<T> = { [K in keyof T]?: T[K]; }; // Required implementation type MyRequired<T> = { [K in keyof T]-?: T[K]; }; // Readonly implementation type MyReadonly<T> = { readonly [K in keyof T]: T[K]; }; // Record implementation type MyRecord<K extends keyof any, T> = { [P in K]: T; }; // Remap keys with `as` type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; type Setters<T> = { [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void; };
infer Keyword
// Extract return type type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; // Extract parameter types as tuple type Parameters<T> = T extends (...args: infer P) => any ? P : never; // Extract first parameter type FirstParameter<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never; // Extract constructor parameters type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never; // Extract instance type type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never; // Extract promise value type type UnwrapPromise<T> = T extends Promise<infer V> ? V : T; // Multiple infer in one condition type Swap<T extends [any, any]> = T extends [infer A, infer B] ? [B, A] : never; type S = Swap<[string, number]>; // [number, string] // Infer in union type UnionToTuple<T> = // complex but useful for exhaustive mapping [T] extends [never] ? [] : never; // simplified
Generic Utility Patterns
// Builder pattern class QueryBuilder<T extends object> { private conditions: Partial<T> = {}; where<K extends keyof T>(key: K, value: T[K]): this { this.conditions[key] = value; return this; } build(): Partial<T> { return { ...this.conditions }; } } // Generic cache class Cache<K, V> { private store = new Map<K, V>(); set(key: K, value: V): void { this.store.set(key, value); } get(key: K): V | undefined { return this.store.get(key); } getOrSet(key: K, factory: () => V): V { if (!this.store.has(key)) this.store.set(key, factory()); return this.store.get(key)!; } } // Event emitter type EventMap = Record<string, unknown[]>; class TypedEmitter<Events extends EventMap> { private listeners = new Map<keyof Events, ((...args: any[]) => void)[]>(); on<K extends keyof Events>(event: K, fn: (...args: Events[K]) => void): this { (this.listeners.get(event) ?? (this.listeners.set(event, []), this.listeners.get(event)!)).push(fn); return this; } emit<K extends keyof Events>(event: K, ...args: Events[K]): void { this.listeners.get(event)?.forEach(fn => fn(...args)); } }
Variance and Covariance
// Covariant position (return types) — subtype is assignable to supertype type Producer<T> = () => T; // Producer<Dog> is assignable to Producer<Animal> (if Dog extends Animal) // Contravariant position (parameter types) — supertype is assignable to subtype type Consumer<T> = (val: T) => void; // Consumer<Animal> is assignable to Consumer<Dog> // in/out variance markers (TS 4.7+) — help with complex generics interface Box<out T> { // covariant: only produces T get(): T; } interface Sink<in T> { // contravariant: only consumes T put(val: T): void; }
Generic Constraints with extends and Conditional
// Ensure T has a particular method function callMethod<T extends { toString(): string }>(val: T): string { return val.toString(); } // Require T to be a constructor function createInstance<T>(ctor: new () => T): T { return new ctor(); } // Narrowing generics function processValue<T>(val: T): T extends string ? number : string { if (typeof val === "string") return val.length as any; return String(val) as any; } // Conditional generic with default type Prettify<T> = { [K in keyof T]: T[K] } & {}; // Forces TS to expand the type when hovering — useful for debugging complex types
Variadic Tuple Types
// Spread in tuple types type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]; type AB = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4] // Prepend type Prepend<H, T extends unknown[]> = [H, ...T]; type P = Prepend<string, [number, boolean]>; // [string, number, boolean] // Generic rest parameters (TS 4.0+) function zip<T extends unknown[], U extends unknown[]>( t: [...T], u: [...U] ): { [K in keyof T]: [T[K], K extends keyof U ? U[K] : never] } { return t.map((v, i) => [v, u[i]]) as any; }