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.
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 member
TypeScript 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.
Master TypeScript advanced type patterns
→ View Course on Udemy — Hands-on video course covering every concept in this post and more.
Sponsored link. We may earn a commission at no extra cost to you.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
