Most TypeScript developers use generics as a slightly smarter version of any. The real power — conditional types, mapped types, template literal types, the infer keyword — goes almost entirely unused. This post covers the patterns that actually eliminate bugs.
⚡ TL;DR: Conditional types (
T extends U ? X : Y) let you compute types at compile time.inferextracts types from other types. Mapped types transform object shapes. Template literal types construct string types programmatically. Together they make illegal states unrepresentable.
The foundation: generic functions and constraints
Everything later in this guide builds on two ideas. A type parameter such as T is a placeholder the caller fills in, usually by inference, so you write a function once and keep the exact type. A constraint written with extends limits what T can be, so the function body can safely use what the constraint guarantees. Without generics you either duplicate the function per type or fall back to any, which throws away the type information.
function first<T>(items: readonly T[]): T | undefined {
return items[0];
}
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const n = first([1, 2, 3]); // number | undefined
const word = longest("hello", "hi"); // string
const list = longest([1, 2, 3], [1, 2]); // number[]
const user = { name: "Alice", age: 30 };
const age = getProperty(user, "age"); // number
// longest(1, 2); // error: number has no 'length'
// getProperty(user, "email"); // error: "email" is not a key of user
console.log(n, word, list, age);K extends keyof T is the pattern behind type-safe property access: the return type T[K] follows the key you pass, and a misspelled key is a compile error rather than undefined at runtime. All of the above compiles under tsc --strict.
- Name type parameters by role:
Tfor a single type,K/Vfor keys and values,Efor elements. - Constrain only as much as the body needs. Over-constraining makes the function harder to reuse.
- If a type parameter appears only once in a signature, you probably don’t need it.
Conditional Types: Type-Level if/else
// Basic conditional type
type IsString = T extends string ? true : false;
type A = IsString; // true
type B = IsString; // false
// Extract return type of any function
type ReturnType = T extends (...args: any[]) => infer R ? R : never;
type Fn = () => { id: number; name: string };
type FnReturn = ReturnType; // { id: number; name: string }
// Distributive conditional types
type ToArray = T extends any ? T[] : never;
type StringOrNumArray = ToArray;
// Result: string[] | number[] NOT (string | number)[] The infer Keyword: Extracting Types from Types
// Extract first argument type
type FirstArg = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type Fn1 = (name: string, age: number) => void;
type Name = FirstArg; // string
// Extract Promise resolved type
type Resolve = T extends Promise ? R : T;
type Resolved = Resolve>; // { id: number }
// Extract array element type
type ElementType = T extends (infer E)[] ? E : never;
type StrEl = ElementType; // string Mapped Types: Transforming Object Shapes
// Remap keys with as clause (TS 4.1+)
type Getters = {
[K in keyof T as `get${Capitalize}`]: () => T[K]
};
type User = { name: string; age: number };
type UserGetters = Getters;
// { getName: () => string; getAge: () => number }
// Filter object keys by value type
type PickByValue = {
[K in keyof T as T[K] extends V ? K : never]: T[K]
};
type OnlyStrings = PickByValue<{ a: string; b: number; c: string }, string>;
// { a: string; c: string }
// Remove readonly
type Mutable = { -readonly [K in keyof T]: T[K] };
// Remove optional
type Required = { [K in keyof T]-?: T[K] }; Template Literal Types: String Type Algebra
// Build string types programmatically
type EventName = `on${Capitalize}`;
type ClickEvent = EventName<'click'>; // 'onClick'
// Generate all combinations
type Direction = 'top' | 'bottom' | 'left' | 'right';
type Padding = `padding${Capitalize}`;
// 'paddingTop' | 'paddingBottom' | 'paddingLeft' | 'paddingRight'
// Type-safe event emitter
type EventMap = { click: MouseEvent; keydown: KeyboardEvent; focus: FocusEvent };
type EventKey = keyof EventMap;
class TypedEmitter {
on(event: K, handler: (e: EventMap[K]) => void): void {}
emit(event: K, data: EventMap[K]): void {}
}
const emitter = new TypedEmitter();
emitter.on('click', (e) => e.clientX); // e is MouseEvent Making Impossible States Unrepresentable
// Bad: allows impossible combinations
type FetchState = {
isLoading: boolean;
data: User | null;
error: Error | null;
};
// Good: discriminated union
type FetchState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: Error };
function render(state: FetchState) {
switch (state.status) {
case 'success': return state.data; // data is User, not User|null
case 'error': return state.error.message; // error is Error
}
}
// Exhaustiveness check
function assertNever(x: never): never {
throw new Error('Unexpected: ' + x);
}
// default: assertNever(state) errors if you miss a union memberTypeScript Generics Cheat Sheet
- ✅ Conditional types:
T extends U ? X : Yfor type-level logic - ✅
inferto extract types from complex type patterns - ✅ Mapped types with
asclause to remap keys - ✅ Template literal types for string type algebra
- ✅ Discriminated unions to make impossible states unrepresentable
- ✅
assertNeverfor exhaustive switch checking - ❌ Never use
ascasts — fix the types instead - ❌ Never reach for
any— useunknownand narrow it
TypeScript generics pair naturally with the V8 JIT optimization guide — TypeScript’s type narrowing produces monomorphic code that V8 compiles fastest. See the generator functions guide for TypeScript async generator typing patterns. External reference: TypeScript handbook on types from types.
Free Weekly Newsletter
🚀 Don’t Miss the Next Cheat Code
You just read something most developers never learn. Get more secrets like this delivered every week — JavaScript internals, Python optimizations, AWS architectures, system design, and AI workflows.
Related: TypeScript Generics Deep Dive: Write Reusable Code With Full Type Safety · TypeScript for JavaScript Developers: The Migration Guide That Actually Works · JavaScript Promises: The Complete Guide From Basics to Advanced Patterns
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
