Generics are the feature that turns TypeScript from a linter into a real type system. Without generics, you either duplicate code for each type or reach for any. With generics, you write code once that works for any type while preserving full type information. This guide covers every generic pattern from basic to advanced.
⚡ TL;DR: Generics are type parameters.
Tis a placeholder for a type the caller provides. Constraints (extends) limit what T can be. Conditional types (T extends U ? X : Y) branch on types. Mapped types transform every property.inferextracts types from other types.
Generic functions — the foundation
// Without generics: duplicate or use any
function firstItem(arr: number[]): number { return arr[0]; }
function firstItemStr(arr: string[]): string { return arr[0]; }
// Or:
function first(arr: any[]): any { return arr[0]; } // Loses type info!
// With generics: works for any type, preserves type info
function first(arr: T[]): T | undefined { return arr[0]; }
const n = first([1, 2, 3]); // TypeScript knows: n is number
const s = first(['a', 'b']); // TypeScript knows: s is string
const u = first([]); // TypeScript knows: u is undefined
Generic constraints
// Constrain T to only types that have certain properties
function longest(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest('hello', 'hi'); // OK: strings have .length
longest([1,2,3], [1,2]); // OK: arrays have .length
longest(1, 2); // Error: numbers have no .length
// Multiple constraints:
function merge(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
// Keyof constraint: only accept valid property names
function getProperty(obj: T, key: K): T[K] {
return obj[key]; // Return type is T[K] — fully typed!
}
const name = getProperty({ name: 'Alice', age: 30 }, 'name'); // string
const age = getProperty({ name: 'Alice', age: 30 }, 'age'); // number
Conditional types
// Conditional types: branch based on type relationship
type IsArray = T extends any[] ? true : false;
type A = IsArray; // true
type B = IsArray; // false
// Unwrap array: get element type
type Unwrap = T extends (infer U)[] ? U : T;
type C = Unwrap; // string
type D = Unwrap; // number (not an array, returns T)
// Extract return type (like built-in ReturnType):
type MyReturnType = T extends (...args: any[]) => infer R ? R : never;
type E = MyReturnType<() => string>; // string
type F = MyReturnType<(n: number) => boolean>; // boolean
Mapped types
// Transform every property in a type
type Readonly = { readonly [K in keyof T]: T[K]; }
type Optional = { [K in keyof T]?: T[K]; }
type Nullable = { [K in keyof T]: T[K] | null; }
// Only string-valued properties:
type StringOnly = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface User { id: number; name: string; email: string; }
type StringProps = StringOnly; // { name: string; email: string; }
Generic patterns cheat sheet
- ✅ Use T for single generic, K/V for key/value pairs, E for elements
- ✅ Constrain with extends to limit what callers can pass
- ✅ Use infer inside conditional types to extract nested types
- ✅ Mapped types to transform all properties at once
- ❌ Never use any when generics can express the type relationship
- ❌ Never over-constrain generics — it reduces reusability
TypeScript generics power the TypeScript migration guide — generics are the first major feature to learn after basic types. External reference: TypeScript Generics handbook.
Recommended Reading
→ Designing Data-Intensive Applications — The essential book every senior developer needs.
→ The Pragmatic Programmer — Timeless engineering wisdom for writing better code.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Don’t Miss the Next Cheat Code
Join 1,000+ senior developers getting expert JS, Python, AWS and system design secrets weekly.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
