How it works
The explanation, with examples you can run.
Reading types off what exists
const sizes = { s: 8, m: 12, l: 16 } as const type Size = keyof typeof sizes // "s" | "m" | "l"type Px = (typeof sizes)[Size] // 8 | 12 | 16 function px(size: Size): number { return sizes[size]}px("m")px("xl") // Error: Argument of type '"xl"' is not assignable to parameter of type '"s" | "m" | "l"'The object is the single source of truth. Add a size and Size grows with it, with no separate list to keep in sync.
Mapped types
A mapped type walks over a union of keys and builds a property for each:
type Flags<T> = { [K in keyof T]: boolean } type User = { name: string; email: string; age: number }type Touched = Flags<User> // { name: boolean; email: boolean; age: boolean }Modifiers can be added or removed (?, readonly, -?), and keys can be renamed with as. Most of what you need is already built in:
| Utility | Gives you |
|---|---|
Partial<T> / Required<T> | Every property optional / required |
Readonly<T> | Every property readonly (checked only, not frozen at runtime) |
Pick<T, K> / Omit<T, K> | Only the keys K / every key except K |
Record<K, V> | An object with keys K and values V |
ReturnType<F> / Parameters<F> | A function type's result / parameter tuple |
Awaited<T> | What a promise resolves to |
Conditional types and infer
type ElementOf<T> = T extends (infer E)[] ? E : never type A = ElementOf<string[]> // stringtype B = ElementOf<number> // never async function loadUser() { return { id: "u1", name: "Ada" }}type LoadedUser = Awaited<ReturnType<typeof loadUser>> // { id: string; name: string }T extends U ? X : Y asks whether T fits U. infer E names a part of the match so the result can use it. When T is a union, the condition is applied to each member separately (it distributes), which is how Exclude<"a" | "b", "a"> gives "b".
Awaited<ReturnType<typeof loadUser>> is the kind of derived type worth writing: the function stays the source of truth, and the type follows it.
Restraint
Each layer of type-level code makes errors longer and the editor slower, and it can only be tested by the compiler. Before writing one, ask whether a plain type alias would do. Prefer the built-in utilities, give derived types names, and keep clever types in one shared file rather than spread through application code.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.