Chapter 25: TypeScript Advanced Types
Advanced Types in TypeScript like we’re sitting together in a quiet room, drawing on a whiteboard, going slowly through the concepts that separate “I know TS” from “I really understand how the type system thinks”.
We’re assuming you already know:
- basic types (string, number, unions , intersections &)
- interfaces vs type aliases
- generics (<T>)
- keyof, basic mapped types like Partial<T>
Advanced types = types computed from other types — using the type system almost like a tiny functional programming language.
These are the pillars you’ll meet in real codebases in 2025–2026 (React, tRPC, Zod-like schemas, API clients, state machines, etc.).
1. Conditional Types — the if/else of the type system
|
0 1 2 3 4 5 6 7 8 9 |
type IsString<T> = T extends string ? "yes" : "no"; type A = IsString<"hello">; // "yes" type B = IsString<123>; // "no" |
Realistic example — Extract the return type of a function (very common pattern)
|
0 1 2 3 4 5 6 7 8 9 |
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type FnReturn = ReturnType<() => string>; // string type PromiseFn = ReturnType<() => Promise<number>>; // Promise<number> |
Nested conditionals + infer (very powerful)
|
0 1 2 3 4 5 6 7 8 9 10 |
type UnwrapPromise<T> = T extends Promise<infer U> ? U extends Promise<infer V> ? UnwrapPromise<V> : U : T; type Deep = UnwrapPromise<Promise<Promise<Promise<string>>>>; // string |
2. Mapped Types — loop over keys of an object type
Basic form:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
type OptionsFlags<T> = { [K in keyof T]: boolean; }; type FeatureFlags = { darkMode: boolean; notifications: boolean; }; type FeatureOptions = OptionsFlags<FeatureFlags>; // { // darkMode: boolean; // notifications: boolean; // } |
Modern mapped types (TS 4.1+) — remapping keys with as clause
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; interface Person { name: string; age: number; } type LazyPerson = Getters<Person>; // { // getName: () => string; // getAge: () => number; // } |
Modifiers — +readonly, -readonly, +?, -?
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
type Mutable<T> = { -readonly [P in keyof T]: T[P]; }; type Required<T> = { [P in keyof T]-?: T[P]; }; |
3. Template Literal Types — string types with interpolation
Introduced in 4.1 — now everywhere in 2026.
|
0 1 2 3 4 5 6 7 8 9 |
type Greeting = `Hello ${string}`; const a: Greeting = "Hello Alice"; // ok // const b: Greeting = "Hi Bob"; // error |
With unions → cartesian product
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
type Event = "click" | "hover" | "focus"; type Modifier = "Ctrl" | "Shift" | ""; type KeyCombo = `${Modifier}${Modifier extends "" ? "" : "+"}${Event}`; // "click" | "hover" | "focus" // | "Ctrl+click" | "Ctrl+hover" | "Ctrl+focus" // | "Shift+click" | "Shift+hover" | "Shift+focus" // | "Ctrl+Shift+click" | "Ctrl+Shift+hover" | "Ctrl+Shift+focus" |
Real use-case — React event handler names
|
0 1 2 3 4 5 6 7 8 |
type DOMEvent = "Click" | "MouseEnter" | "KeyDown"; type ReactHandler = `on${DOMEvent}`; // "onClick" | "onMouseEnter" | "onKeyDown" |
Path / dot notation types (very common in form libraries, tRPC, TanStack Table)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
type Paths<T> = T extends object ? { [K in keyof T]: K extends string | number ? `${K}` | (T[K] extends object ? `${K}.${Paths<T[K]>}` : never) : never; }[keyof T] : never; type User = { id: number; profile: { name: string; address: { city: string }; }; }; type UserPath = Paths<User>; // "id" | "profile" | "profile.name" | "profile.address" | "profile.address.city" |
4. infer in Conditional Types — extract pieces dynamically
infer = declare a type variable inside a conditional.
Classic example — flatten array type
|
0 1 2 3 4 5 6 7 8 9 |
type Flatten<T> = T extends Array<infer U> ? U : T; type A = Flatten<string[]>; // string type B = Flatten<number[][]>; // number[] |
More advanced — infer from function return
|
0 1 2 3 4 5 6 7 8 9 |
type UnpackReturn<T> = T extends { (): infer R } ? R : never; type Fn = () => { data: string[] }; type Data = UnpackReturn<Fn>; // { data: string[] } |
5. satisfies operator (TS 4.9 → very loved in 2025–2026)
Validates shape without widening to the declared type.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
type Colors = { primary: string; accent?: string; danger: string; }; const theme = { primary: "#0070f3", accent: "cyan", danger: "#ff0000", warning: "#ffa500", // extra key — allowed } satisfies Colors; // theme.primary → "#0070f3" (literal — not just string!) // theme.warning → error (good!) |
Perfect for: themes, configs, button variants, i18n keys, feature flags.
6. Combining everything — real production patterns
Pattern A: Deep partial (recursive Partial)
|
0 1 2 3 4 5 6 7 8 |
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T; |
Pattern B: Mutable version of readonly type
|
0 1 2 3 4 5 6 7 8 |
type MutableDeep<T> = T extends object ? { -readonly [P in keyof T]: MutableDeep<T[P]> } : T; |
Pattern C: API response with status + data (conditional + infer)
|
0 1 2 3 4 5 6 7 8 9 10 |
type ApiResponse<T> = | { status: "success"; data: T } | { status: "error"; error: string }; type SuccessData<R> = R extends { status: "success"; data: infer D } ? D : never; |
Pattern D: String literal union from const object (with satisfies)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
const actions = { fetch: "FETCH", success: "SUCCESS", failure: "FAILURE", } as const satisfies Record<string, string>; type ActionType = (typeof actions)[keyof typeof actions]; // "FETCH" | "SUCCESS" | "FAILURE" |
Quick Reference Table (2026 edition)
| Concept | Syntax example | Best real use-case | Since TS version |
|---|---|---|---|
| Conditional Types | T extends U ? X : Y | Extract, filter, transform | 2.8 |
| infer | infer U inside conditional | Unwrap Promise, Array, ReturnType | 2.8 |
| Mapped Types | { [K in keyof T]: … } | Partial, Required, Readonly, Pick/Omit clones | 2.1 |
| Key remapping as | [K in keyof T as NewName] | Getters, prefixed keys, uppercase | 4.1 |
| Template Literal Types | $$ {Prefix} $${T} |
Event names, paths, CSS classes, i18n keys | 4.1 |
| satisfies | value satisfies Type | Configs, themes, variants (keep literals) | 4.9 |
| Recursive types | Self-reference in conditional/mapped | Trees, nested forms, deep partial | 4.1+ |
Your next steps / homework
- Open TS playground
- Try to write:
- DeepReadonly<T>
- Paths<T> for a nested object
- A RouteParams type that infers from a path literal like “/users/:id/posts/:postId”
- Use satisfies on a theme object and try accessing an extra property → see the error
Which part feels most exciting or most confusing right now?
Want to:
- Go deeper into recursive conditional types with examples
- Build a tiny form path / field name type system
- See how tRPC / Zod use these under the hood
- Common mistakes & anti-patterns with advanced types
Just say the word — we’ll zoom in exactly there! 😄
