Technology · TypeScript
TypeScript Advanced Types
Learn conditional types, mapped types, template literals, and advanced pattern matching.
TL;DR
- 01Use conditional types for type-level if-else logic.
- 02Use mapped types to transform keys and values in existing types.
- 03Use template literal types for string manipulation at the type level.
Conditional Types
- Use conditional types with the ternary syntax for type-level decisions.
type IsString<T> = T extends string ? true : false; type A = IsString<"hello">; // true type B = IsString<number>; // false - Check constraints and narrow types based on conditions.
type Flatten<T> = T extends Array<infer U> ? U : T; type Str = Flatten<string[]>; // string type Num = Flatten<number>; // number - Use the infer keyword to extract types from generic patterns.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type NumReturn = ReturnType<() => number>; // number - Distribute conditional types over union types automatically.
type ToArray<T> = T extends any ? T[] : never; type Result = ToArray<string | number>; // string[] | number[]
Mapped Types
- Transform every key in an object type to a new shape.
type Readonly<T> = { readonly [K in keyof T]: T[K]; }; type User = { name: string; age: number }; type ReadonlyUser = Readonly<User>; // { readonly name: string; readonly age: number } - Make all properties optional using mapped types.
type Optional<T> = { [K in keyof T]?: T[K]; }; - Extract values and create a mapped type from them.
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; - Use as clauses to rename keys during mapping.
type Getters<T> = { [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K]; }; type UserGetters = Getters<User>; // { getName: () => string; getAge: () => number }
Template Literal Types
- Build string types using template literals.
type Event<T extends string> = `on${Capitalize<T>}`; type ClickEvent = Event<"click">; // "onClick" type ChangeEvent = Event<"change">; // "onChange" - Combine and manipulate strings at the type level.
type Path = "user" | "post" | "comment"; type GetRoute = `GET /${Path}`; // "GET /user" | "GET /post" | "GET /comment" - Extract parts of string types with conditional types.
type Split<T extends string, D extends string> = T extends `${infer F}${D}${infer R}` ? [F, ...Split<R, D>] : [T]; type Parts = Split<"a-b-c", "-">; // ["a", "b", "c"]
Advanced Utility Patterns
- Build sophisticated utility types combining multiple features.
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T; - Create recursive type transformations.
type DeepReadonly<T> = T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T; - Compose multiple conditional types for complex logic.
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T extends object ? U extends object ? Flatten<U> : T : T; - Use type predicates to narrow based on relationships.
type IsAssignableTo<T, U> = T extends U ? true : false;
Practical Applications
- Build type-safe API response handlers with conditional types.
type ApiResponse<T extends "success" | "error"> = T extends "success" ? { status: 200; data: unknown } : { status: 400; error: string }; - Create database query builders with mapped and conditional types.
type SelectBuilder<T> = { [K in keyof T as `select${Capitalize<K & string>}`]: () => void; }; - Build framework-like type systems with advanced features.
type Validator<T> = { [K in keyof T]: (v: T[K]) => boolean; };
Tip: Use conditional types with infer to extract and reuse parts of complex types in your type transformations.
Warning: Advanced types are powerful but can become hard to read — document complex type transformations and keep them focused on single concerns.