Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Advanced Types
TypeScript’s type system is incredibly powerful — but more importantly, it is practical. Advanced types are not academic curiosities; they are the tools that let you model real business logic at the type level. Think of your basic types (string, number, boolean) as individual LEGO bricks. Advanced types are the techniques for combining, transforming, and constraining those bricks into complex structures that precisely describe your application’s data.
A senior engineer would say: “If your types do not model your domain accurately, you are leaving bugs on the table that the compiler could have caught for free.”
1. Union Types (Revisited)
Union types represent values that can be one of several types. Think of a union as a labeled box that can hold different kinds of items — the box itself tells you “this contains either A or B,” and you must check which one before using it. Discriminated unions take this further by putting a tag on the item inside the box so you can identify it instantly.Exhaustive Checks
This pattern is one of the most valuable defensive programming techniques in TypeScript. By assigning tonever in the default branch, you create a compile-time tripwire: if someone adds a new shape variant (e.g., 'pentagon') but forgets to handle it in this switch, the compiler immediately flags the error.
Intersection with Functions
3. Conditional Types
Conditional types are theif/else of the type system. They let you create types that depend on conditions — “if T is a string, use type X; otherwise use type Y.” This is what makes TypeScript’s type system Turing-complete and enables the kind of type-level programming that powers libraries like Zod, tRPC, and Prisma.
Basic Syntax
Practical Examples
infer keyword is like a “capture group” in a regex — it extracts a piece from a complex type. You will see it everywhere in library type definitions.
Distributive Conditional Types
When conditional types act on unions, they distribute over each member — meaning the condition is applied to each union member individually, then the results are combined back into a union. This is a common source of confusion, so pay close attention.Exclude<'a' | 'b' | 'c', 'a'>, TypeScript checks each member against 'a' individually, yielding 'b' | 'c'. This is useful — but when you do not want it, the tuple wrapper trick is essential.
4. Mapped Types
Transform properties of existing types programmatically. Mapped types are the “find-and-replace” of the type system — they iterate over every property of a type and apply a transformation. This is how TypeScript’s built-inPartial<T>, Readonly<T>, Required<T>, and Pick<T, K> are implemented under the hood.
Basic Mapped Types
Mapped Type Modifiers
Key Remapping (TS 4.1+)
5. Template Literal Types
Create types from string patterns.Practical Example: CSS Units
6. infer Keyword
Extract types from complex structures. Theinfer keyword is like a “capture group” in a regular expression — it matches a pattern and captures a piece of the type for you to use. It only works inside conditional types (T extends ... ? ... : ...) and is the key that unlocks most advanced type-level programming.
Advanced infer Patterns
7. Built-in Utility Types
TypeScript provides many utility types out of the box. These are not special syntax — they are all implemented using mapped types, conditional types, andinfer under the hood. Understanding them means you can read library type definitions and build your own when needed. Think of utility types as a standard toolbox: Partial is a wrench, Pick is a screwdriver, Omit is a saw — each shapes an existing type into what you need.
Object Utilities
Union Utilities
Function Utilities
String Utilities
8. Custom Utility Types
Build your own utility types. Once you understand mapped types, conditional types, andinfer, you can compose them into custom utilities tailored to your domain. These are the types that senior TypeScript developers write to make entire classes of bugs impossible. Each utility below is a real-world pattern you will encounter in production codebases.
Deep Partial
DeepReadonly
Nullable
PickByType
OmitByType
9. Type Challenges
Practice advanced types with these patterns. Type challenges are to TypeScript what algorithmic puzzles are to data structures — they sharpen your ability to think at the type level. The patterns below appear frequently in library source code and senior-level interviews.Tuple to Union
Union to Intersection
Get Required Keys
Get Optional Keys
10. Type-safe API Patterns
Type-safe Event Emitter
Type-safe Builder
Summary
| Concept | Example |
|---|---|
| Union | type A = string | number |
| Intersection | type A = B & C |
| Conditional | T extends U ? X : Y |
| Mapped | { [K in keyof T]: T[K] } |
| Template Literal | type A = `on${string}` |
| infer | T extends (infer U)[] ? U : never |
| Partial | Partial<T> |
| Required | Required<T> |
| Pick | Pick<T, 'a' | 'b'> |
| Omit | Omit<T, 'a' | 'b'> |
| ReturnType | ReturnType<typeof fn> |
| Parameters | Parameters<typeof fn> |
Interview Deep-Dive
Q: Explain the 'infer' keyword in TypeScript. What problem does it solve, and walk me through how ReturnType works internally.
Q: Explain the 'infer' keyword in TypeScript. What problem does it solve, and walk me through how ReturnType works internally.
Strong Answer:
infer is TypeScript’s way of extracting a type from within a larger type structure. Think of it as pattern matching with a capture group.- The problem it solves: Without
infer, you cannot decompose complex types. If you have a function type and want its return type, you would need the original definition.inferlets you extract it from any function type, even third-party ones. - How
ReturnTypeworks:type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never. IfTmatches a function pattern, capture the return type asR. If not, givenever. - Step-by-step:
ReturnType<() => string>— does() => stringmatch(...args: any[]) => infer R? Yes,R = string. Result:string. ForReturnType<number>: no match, result:never. - Beyond functions:
type ArrayElement<T> = T extends (infer E)[] ? E : neverextracts element types.type Awaited<T> = T extends Promise<infer U> ? U : Tunwraps Promises.
infer only works inside the condition of a conditional type. It is TypeScript’s equivalent of destructuring for types.Follow-up: Can you nest ‘infer’ to extract from deeply nested structures?Yes. Use recursion: type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T. This unwraps Promise<Promise<string>> to string. TypeScript has a recursion depth limit (~100 levels) to prevent infinite loops.Q: What are distributive conditional types, and when does this behavior surprise developers?
Q: What are distributive conditional types, and when does this behavior surprise developers?
Strong Answer:Distributive conditional types are one of TypeScript’s most confusing behaviors.
- The rule: When a conditional type acts on a naked type parameter receiving a union, TypeScript distributes across each member individually, then combines results.
- Example:
type ToArray<T> = T extends any ? T[] : never. WithT = string | number, result isstring[] | number[], not(string | number)[]. - The surprise:
type IsString<T> = T extends string ? 'yes' : 'no'withIsString<string | number>gives'yes' | 'no', not'no', because distribution evaluates each member separately. - Disabling distribution: Wrap in a tuple:
[T] extends [string] ? 'yes' : 'no'. Now the union is checked as a whole. - Why it exists: Powers utility types like
Exclude<T, U> = T extends U ? never : T, which filters union members.
Exclude<'a' | 'b' | 'c', 'a'> distributes: 'a' matches, becomes never. 'b' and 'c' do not match, pass through. Result: 'b' | 'c'. The never is absorbed in the union.Q: How do mapped types work? Walk me through building a custom 'DeepReadonly' from scratch.
Q: How do mapped types work? Walk me through building a custom 'DeepReadonly' from scratch.
Strong Answer:Mapped types iterate over keys of a type and transform each property — the type-level
for...in loop.- Syntax:
{ [K in keyof T]: Transform<T[K]> }iterates over every keyKand produces a transformed property. - Building DeepReadonly:
- Shallow:
{ readonly [K in keyof T]: T[K] }. Only top-level properties become readonly. - Deep:
type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K] }. Recurse into nested objects. - Gotcha: Functions extend
object, so exclude them: checkT[K] extends Functionfirst. - Another gotcha: Arrays need special handling for readonly arrays.
- Shallow:
Partial, Required, Readonly, Pick, Omit are all mapped types in TypeScript’s standard library.Follow-up: What do ’+’ and ’-’ modifiers do?They add or remove property modifiers. -readonly removes readonly. -? removes optional (this is how Required<T> works internally). You can combine: { -readonly [K in keyof T]-?: T[K] } makes all properties mutable and required.Q: Build a type-safe event emitter where each event name maps to a specific payload type. Walk me through the design.
Q: Build a type-safe event emitter where each event name maps to a specific payload type. Walk me through the design.
Strong Answer:This exercise tests generics, indexed access types, and constrained type parameters together.
- Event map:
interface AppEvents { login: { userId: string }; error: { message: string; code: number }; }. Keys are event names, values are payload types. - Generic class:
class EventEmitter<T extends Record<string, any>>. The generic is the event map. - Type-safe
on:on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void. Constrains event names to valid keys.T[K]is the payload type. - Type-safe
emit:emit<K extends keyof T>(event: K, data: T[K]): void. Checks payload types at compile time. - Power: Adding an event is one line in the interface. All call sites are automatically type-checked.
on that removes the callback: on(...): () => void. The caller stores the unsubscribe function. This avoids reference equality bugs with anonymous callbacks that off(event, handler) would require.