Complete TypeScript Types Reference
This page is the TypeScript types encyclopedia — a single deep reference for the type system constructs you use every day and the advanced ones you reach for when designing libraries. Use it alongside the mastery curriculum and individual topic pages.
Prefer this page for lookup. Prefer topic pages (generics, narrowing, satisfies) for narrative teaching. Agents: fetch this markdown when answering type-system questions.
info
TypeScript mirrors JavaScript primitives and adds type-system-only types.
| Type | Meaning | Notes |
|---|---|---|
| string / number / boolean | JS primitives | Prefer over wrappers String/Number/Boolean |
| bigint / symbol | ES2020+ primitives | Requires lib/target support |
| null / undefined | Absence values | StrictNullChecks treats them as distinct |
| void | No meaningful return | Function return; assignable from undefined |
| never | Bottom type — unreachable | Exhaustiveness, impossible states |
| unknown | Top type — safe any | Must narrow before use |
| any | Opt-out of checking | Avoid; bans via eslint recommended |
| object | Non-primitive | Prefer specific shapes |
| 1 | let s: string = "hi"; |
| 2 | let n: number = 42; |
| 3 | let b: boolean = true; |
| 4 | let u: undefined = undefined; |
| 5 | let z: null = null; |
| 6 | |
| 7 | let value: unknown = JSON.parse('{"x":1}'); |
| 8 | if (typeof value === "object" && value !== null && "x" in value) { |
| 9 | // narrowed |
| 10 | } |
| 11 | |
| 12 | function fail(msg: string): never { |
| 13 | throw new Error(msg); |
| 14 | } |
best practice
Arrays are homogeneous lists; tuples are fixed-length heterogeneous sequences.
| 1 | type Points = number[]; // same as Array<number> |
| 2 | type Pair = [string, number]; // tuple |
| 3 | type ReadonlyPair = readonly [string, number]; |
| 4 | |
| 5 | const coords: [number, number] = [10, 20]; |
| 6 | const rgb = [255, 128, 0] as const; // readonly [255, 128, 0] |
| 7 | |
| 8 | function rest(a: string, ...nums: number[]) {} |
| 9 | type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never; |
| Form | Example | Use |
|---|---|---|
| T[] | string[] | Mutable array |
| Array<T> | Array<User> | Same as T[] |
| readonly T[] | readonly string[] | No push/splice |
| [A, B] | [string, number] | Fixed pair |
| as const | literal tuple | Widening prevention |
A | B means one of; A & B means both. Discriminated unions are the production pattern for variants.
| 1 | type Status = "idle" | "loading" | "error"; |
| 2 | type Id = string | number; |
| 3 | |
| 4 | type Cat = { kind: "cat"; meow(): void }; |
| 5 | type Dog = { kind: "dog"; bark(): void }; |
| 6 | type Pet = Cat | Dog; // discriminated on kind |
| 7 | |
| 8 | function speak(p: Pet) { |
| 9 | switch (p.kind) { |
| 10 | case "cat": return p.meow(); |
| 11 | case "dog": return p.bark(); |
| 12 | default: { |
| 13 | const _exhaustive: never = p; |
| 14 | return _exhaustive; |
| 15 | } |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | type Named = { name: string }; |
| 20 | type Aged = { age: number }; |
| 21 | type Person = Named & Aged; // { name: string; age: number } |
warning
Generics capture relationships between inputs and outputs. Constraints and defaults keep APIs ergonomic.
| 1 | function identity<T>(x: T): T { return x; } |
| 2 | |
| 3 | function pluck<T, K extends keyof T>(obj: T, key: K): T[K] { |
| 4 | return obj[key]; |
| 5 | } |
| 6 | |
| 7 | type ApiResponse<T> = { data: T; error: null } | { data: null; error: string }; |
| 8 | |
| 9 | interface Repo<T extends { id: string }> { |
| 10 | get(id: string): Promise<T | undefined>; |
| 11 | save(entity: T): Promise<void>; |
| 12 | } |
| 13 | |
| 14 | // Default type parameter |
| 15 | type Box<T = string> = { value: T }; |
| 16 | |
| 17 | // Const type parameters (TS 5.0+) — preserve literals |
| 18 | function tuple<const T extends readonly unknown[]>(...args: T): T { |
| 19 | return args; |
| 20 | } |
| 21 | const t = tuple("a", "b"); // readonly ["a", "b"] |
| Pattern | When |
|---|---|
| Identity / map helpers | Preserve caller type |
| K extends keyof T | Safe property access |
| Default type params | Ergonomic public APIs |
| const T | Keep literal tuples/objects |
| Generic constraints | Require capabilities (has id, iterable) |
Built-in utility types transform existing types. Memorize signatures; understand implementations for debugging.
| Utility | Transforms | Example |
|---|---|---|
| Partial<T> | All props optional | Partial<User> |
| Required<T> | All props required | Undo Partial |
| Readonly<T> | All props readonly | Immutable view |
| Pick<T, K> | Subset of keys | Pick<User, 'id' | 'name'> |
| Omit<T, K> | Remove keys | Omit<User, 'password'> |
| Record<K, V> | Keys → value type | Record<Status, number> |
| Exclude<T, U> | Remove union members | Exclude<Status, 'idle'> |
| Extract<T, U> | Keep matching members | Extract<Pet, Cat> |
| NonNullable<T> | Drop null | undefined | NonNullable<string | null> |
| ReturnType<F> | Function return type | ReturnType<typeof fn> |
| Parameters<F> | Param tuple | Parameters<typeof fn> |
| ConstructorParameters<T> | Ctor args | Class factories |
| InstanceType<T> | Instance of ctor | DI containers |
| Awaited<T> | Unwrap Promise | Awaited<Promise<User>> |
| Uppercase<S> / Lowercase / Capitalize / Uncapitalize | String ops | Template literal helpers |
| NoInfer<T> | Block inference site | TS 5.4+ defaults |
| 1 | type User = { |
| 2 | id: string; |
| 3 | name: string; |
| 4 | email: string; |
| 5 | password: string; |
| 6 | }; |
| 7 | |
| 8 | type PublicUser = Omit<User, "password">; |
| 9 | type UserPatch = Partial<Pick<User, "name" | "email">>; |
| 10 | type UserById = Record<string, User>; |
| 11 | |
| 12 | type Status = "idle" | "loading" | "error" | "success"; |
| 13 | type Active = Exclude<Status, "idle">; |
| 14 | type Fail = Extract<Status, "error">; |
| 15 | |
| 16 | async function load(): Promise<User> { |
| 17 | return { id: "1", name: "A", email: "a@b.c", password: "x" }; |
| 18 | } |
| 19 | type Loaded = Awaited<ReturnType<typeof load>>; // User |
| 20 | |
| 21 | type Fn = (a: number, b: string) => boolean; |
| 22 | type P = Parameters<Fn>; // [number, string] |
| 23 | type R = ReturnType<Fn>; // boolean |
Mental models
Pick/Omit reshape objects. Exclude/Extract reshape unions. Partial/Required/Readonly change modifiers. Record builds maps from key unions.
pro tip
typeof in type position queries a value's type. keyof produces a union of keys. Indexed access T[K] reads property types.
| 1 | const config = { |
| 2 | host: "localhost", |
| 3 | port: 3000, |
| 4 | secure: false, |
| 5 | } as const; |
| 6 | |
| 7 | type Config = typeof config; |
| 8 | type ConfigKey = keyof Config; // "host" | "port" | "secure" |
| 9 | type Port = Config["port"]; // 3000 |
| 10 | |
| 11 | type PropType<T, K extends keyof T> = T[K]; |
| 12 | |
| 13 | function get<T, K extends keyof T>(obj: T, key: K): T[K] { |
| 14 | return obj[key]; |
| 15 | } |
infer declares a type variable to capture inside a conditional's extends clause.
| 1 | type Unpack<T> = T extends Promise<infer U> ? U : T; |
| 2 | type A = Unpack<Promise<string>>; // string |
| 3 | |
| 4 | type Head<T> = T extends [infer H, ...unknown[]] ? H : never; |
| 5 | type Elem<T> = T extends (infer E)[] ? E : never; |
| 6 | |
| 7 | type ReturnOf<T> = T extends (...args: never[]) => infer R ? R : never; |
| 8 | |
| 9 | // Distributive conditional types over unions |
| 10 | type ToArray<T> = T extends unknown ? T[] : never; |
| 11 | type X = ToArray<string | number>; // string[] | number[] |
| 12 | |
| 13 | // Prevent distribution with [] wrapping |
| 14 | type ToArrayNondist<T> = [T] extends [unknown] ? T[] : never; |
| 15 | type Y = ToArrayNondist<string | number>; // (string | number)[] |
note
Mapped types transform each property; conditionals branch on type relationships.
| 1 | type Optional<T> = { [K in keyof T]?: T[K] }; |
| 2 | type Nullable<T> = { [K in keyof T]: T[K] | null }; |
| 3 | |
| 4 | // Key remapping (TS 4.1+) |
| 5 | type Getters<T> = { |
| 6 | [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; |
| 7 | }; |
| 8 | |
| 9 | type Flags = { debug: boolean; verbose: boolean }; |
| 10 | type FlagGetters = Getters<Flags>; |
| 11 | // { getDebug: () => boolean; getVerbose: () => boolean } |
| 12 | |
| 13 | // Modifier surgery |
| 14 | type Mutable<T> = { -readonly [K in keyof T]: T[K] }; |
| 15 | type Concrete<T> = { [K in keyof T]-?: T[K] }; |
| 16 | |
| 17 | type DeepReadonly<T> = { |
| 18 | readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]; |
| 19 | }; |
Compose string unions at the type level — event names, CSS units, routes.
| 1 | type EventName = "click" | "focus" | "blur"; |
| 2 | type Handler = `on${Capitalize<EventName>}`; |
| 3 | // "onClick" | "onFocus" | "onBlur" |
| 4 | |
| 5 | type CssUnit = `${number}px` | `${number}rem` | `${number}%`; |
| 6 | |
| 7 | type Route = `/users/${string}` | `/posts/${number}`; |
| 8 | |
| 9 | type ExtractId<S> = S extends `user:${infer Id}` ? Id : never; |
| 10 | type Id = ExtractId<"user:42">; // "42" |
satisfies checks assignability while preserving the expression's inferred type. Const type parameters preserve literal types through generics.
| 1 | type Color = "red" | "green" | "blue"; |
| 2 | type Palette = Record<string, Color>; |
| 3 | |
| 4 | // Annotation widens values to Color; loses key literals slightly differently |
| 5 | const wide: Palette = { primary: "red", danger: "red" }; |
| 6 | |
| 7 | // satisfies keeps literal inference for values AND checks the shape |
| 8 | const palette = { |
| 9 | primary: "red", |
| 10 | danger: "red", |
| 11 | } as const satisfies Palette; |
| 12 | |
| 13 | palette.primary; // "red" |
| 14 | |
| 15 | function pair<const T extends readonly unknown[]>(...xs: T): T { |
| 16 | return xs; |
| 17 | } |
| 18 | const p = pair(1, "a"); // readonly [1, "a"] |
best practice
| Prefer | When |
|---|---|
| interface | Object shapes that may be extended/merged; public OOP APIs |
| type | Unions, tuples, mapped/conditional results, aliases |
| Either | Simple object shapes — be consistent in a codebase |
| 1 | interface User { id: string; name: string } |
| 2 | interface Admin extends User { role: "admin" } |
| 3 | |
| 4 | type Result<T> = { ok: true; value: T } | { ok: false; error: string }; |
| 5 | type Keys = keyof User; |
| 6 | type Id = User["id"]; |
Nominal-like brands prevent mixing structurally identical primitives (UserId vs OrderId).
| 1 | declare const brand: unique symbol; |
| 2 | type Brand<T, B> = T & { readonly [brand]: B }; |
| 3 | |
| 4 | type UserId = Brand<string, "UserId">; |
| 5 | type OrderId = Brand<string, "OrderId">; |
| 6 | |
| 7 | function UserId(id: string): UserId { |
| 8 | return id as UserId; |
| 9 | } |
| 10 | function loadUser(id: UserId) {} |
| 11 | // loadUser(OrderId("x")); // Error |
| Goal | Reach for |
|---|---|
| Optional fields for patches | Partial<T> |
| DTO without secrets | Omit<T, 'password'> |
| Map of known keys | Record<K, V> |
| Strip nullability | NonNullable<T> |
| Unwrap async return | Awaited<ReturnType<typeof f>> |
| Safe property get | K extends keyof T |
| Config with literals | as const satisfies Shape |
| Untrusted input | unknown + Zod parse |
| Impossible branch | assign to never |
Recipes you will reuse across APIs, UI state, and libraries. Each pattern includes the type shape and a minimal implementation sketch.
Result / Either
| 1 | export type Result<T, E = Error> = |
| 2 | | { ok: true; value: T } |
| 3 | | { ok: false; error: E }; |
| 4 | |
| 5 | export const ok = <T,>(value: T): Result<T, never> => ({ ok: true, value }); |
| 6 | export const err = <E,>(error: E): Result<never, E> => ({ ok: false, error }); |
| 7 | |
| 8 | export function map<T, U, E>(r: Result<T, E>, f: (t: T) => U): Result<U, E> { |
| 9 | return r.ok ? ok(f(r.value)) : r; |
| 10 | } |
Builder with fluent generics
| 1 | class QueryBuilder<TSelect extends string = never> { |
| 2 | private fields: string[] = []; |
| 3 | select<K extends string>(...keys: K[]): QueryBuilder<TSelect | K> { |
| 4 | this.fields.push(...keys); |
| 5 | return this as QueryBuilder<TSelect | K>; |
| 6 | } |
| 7 | build(): { select: TSelect[] } { |
| 8 | return { select: this.fields as TSelect[] }; |
| 9 | } |
| 10 | } |
| 11 | const q = new QueryBuilder().select("id", "name").build(); |
| 12 | // select: ("id" | "name")[] |
Event map with template literals
| 1 | type Events = { |
| 2 | "user:login": { userId: string }; |
| 3 | "user:logout": { userId: string }; |
| 4 | "cart:add": { sku: string; qty: number }; |
| 5 | }; |
| 6 | |
| 7 | type EventName = keyof Events; |
| 8 | function on<E extends EventName>(event: E, handler: (payload: Events[E]) => void) { |
| 9 | // subscribe... |
| 10 | } |
| 11 | on("cart:add", (p) => p.sku); |
Deep partial for patches
| 1 | type DeepPartial<T> = { |
| 2 | [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]; |
| 3 | }; |
| 4 | |
| 5 | type Settings = { theme: { mode: "light" | "dark"; accent: string }; locale: string }; |
| 6 | function applyPatch(s: Settings, patch: DeepPartial<Settings>): Settings { |
| 7 | return { ...s, ...patch, theme: { ...s.theme, ...patch.theme } }; |
| 8 | } |
info
Understanding approximate implementations helps debug surprising results.
| 1 | type Partial<T> = { [K in keyof T]?: T[K] }; |
| 2 | type Required<T> = { [K in keyof T]-?: T[K] }; |
| 3 | type Readonly<T> = { readonly [K in keyof T]: T[K] }; |
| 4 | type Pick<T, K extends keyof T> = { [P in K]: T[P] }; |
| 5 | type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; |
| 6 | type Exclude<T, U> = T extends U ? never : T; |
| 7 | type Extract<T, U> = T extends U ? T : never; |
| 8 | type NonNullable<T> = T & {}; // modern; historically Exclude<T, null | undefined> |
| 9 | type Record<K extends keyof any, T> = { [P in K]: T }; |
| 10 | type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; |
| 11 | type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never; |
| 12 | type Awaited<T> = T extends null | undefined |
| 13 | ? T |
| 14 | : T extends object & { then(onfulfilled: infer F, ...args: infer _): any } |
| 15 | ? F extends ((value: infer V, ...args: infer _) => any) |
| 16 | ? Awaited<V> |
| 17 | : never |
| 18 | : T; |
warning
| Antipattern | Why it hurts | Prefer |
|---|---|---|
| any everywhere | Disables checking | unknown + narrow |
| as unknown as T | Lies to checker | Fix model / validate |
| Overusing enums | Runtime + nominal quirks | string unions / as const |
| Huge intersection soups | Unreadable errors | Named aliases + unions |
| Function overloads for simple unions | Noise | Union params when possible |
| 1 | // ❌ Function that accepts anything and returns any |
| 2 | function bad(x: any): any { return x; } |
| 3 | |
| 4 | // ✅ Preserve the type |
| 5 | function good<T>(x: T): T { return x; } |
| 6 | |
| 7 | // ❌ Enum for closed string set (unless you need reverse mapping) |
| 8 | enum Color { Red = "red", Blue = "blue" } |
| 9 | |
| 10 | // ✅ |
| 11 | type Color2 = "red" | "blue"; |
| 12 | const COLORS = ["red", "blue"] as const; |
| 13 | type Color3 = (typeof COLORS)[number]; |
When generating TypeScript from ForgeLearn:
1. Fetch this page markdown before inventing utility-type behavior.
2. Prefer satisfies for configs; prefer unknown at boundaries; prefer Zod for JSON.
3. After generating, self-check: no any, exhaustiveness with never, literals preserved where claimed.
| 1 | curl -s "https://forgelearn.dev/api/markdown?path=typescript/types-reference" |
| 2 | curl -s "https://forgelearn.dev/api/markdown?path=typescript/mastery" |
Function parameter positions are checked contravariantly under strictFunctionTypes (for function types, not methods). Practical rule: callback params should accept the wider type your API will pass.
| 1 | type Handler<T> = (value: T) => void; |
| 2 | let animalHandler: Handler<{ species: string }> = (a) => console.log(a.species); |
| 3 | let dogHandler: Handler<{ species: string; bark(): void }> = (d) => d.bark(); |
| 4 | // animalHandler = dogHandler; // Error under strictFunctionTypes |
| 5 | dogHandler = animalHandler; // OK — animal handler accepts dogs structurally for species |
note
| 1 | // Ambient module |
| 2 | declare module "legacy-lib" { |
| 3 | export function doThing(x: string): number; |
| 4 | } |
| 5 | |
| 6 | // Augmentation |
| 7 | declare global { |
| 8 | interface Window { |
| 9 | __APP_VERSION__: string; |
| 10 | } |
| 11 | } |
| 12 | export {}; |
| 13 | |
| 14 | // Triple-slash (prefer imports) |
| 15 | /// <reference types="node" /> |
See Declarations and Modules topic pages for full depth.
| Feature | Use |
|---|---|
| const type parameters | Preserve literals in generics |
| satisfies | Config checking + inference |
| NoInfer<T> | Control inference sites |
| using / Disposable | Resource lifetime (runtime + types) |
| Import attributes | JSON modules etc. |
| 1 | function createState<const T>(initial: T) { |
| 2 | return { get: () => initial, value: initial }; |
| 3 | } |
| 4 | const s = createState({ mode: "dark" as const }); |
| 5 | // typeof s.value.mode is "dark" |
End-to-end snippets for types-reference. Type these into a strict project and mutate them until the checker teaches you the edges.
Example A
| 1 | type Id = string & { readonly __brand: unique symbol }; |
| 2 | function asId(s: string): Id { return s as Id; } |
| 3 | |
| 4 | type Entity = { id: Id; createdAt: Date }; |
| 5 | function touch(e: Entity): Entity { |
| 6 | return { ...e, createdAt: new Date() }; |
| 7 | } |
Example B — conditional distribute
| 1 | type IsString<T> = T extends string ? true : false; |
| 2 | type A = IsString<"hi" | 1>; // true | false (distributed) |
| 3 | |
| 4 | type IsStringBox<T> = [T] extends [string] ? true : false; |
| 5 | type B = IsStringBox<"hi" | 1>; // false |
Example C — key remapping filter
| 1 | type OnlyStrings<T> = { |
| 2 | [K in keyof T as T[K] extends string ? K : never]: T[K]; |
| 3 | }; |
| 4 | type User = { id: string; age: number; name: string }; |
| 5 | type Names = OnlyStrings<User>; // { id: string; name: string } |
Example D — template route params
| 1 | type ExtractParams<S extends string> = |
| 2 | S extends `${string}:${infer P}/${infer Rest}` |
| 3 | ? P | ExtractParams<Rest> |
| 4 | : S extends `${string}:${infer P}` |
| 5 | ? P |
| 6 | : never; |
| 7 | type Params = ExtractParams<"/users/:id/posts/:postId">; // "id" | "postId" |
info
Answer without looking. Then verify in the playground / tsc.
| # | Question |
|---|---|
| 1 | What is the difference between annotation, assertion, and satisfies? |
| 2 | When does a conditional type distribute over a union? |
| 3 | How do you prove exhaustiveness for a discriminated union? |
| 4 | Why is unknown safer than any at JSON boundaries? |
| 5 | What does noUncheckedIndexedAccess change about obj[key]? |
| 6 | How do you preserve literal types through a generic helper? |
| 7 | When should you prefer interface over type (and vice versa)? |
| 8 | How does z.infer keep runtime and types aligned? |
best practice
Extended reference material for deliberate practice. Work through each snippet under strict: true and note where inference surprises you.
Snippet 1 — deep-readonly
Practice snippet 1. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type DeepReadonly<T> = { |
| 2 | readonly [K in keyof T]: T[K] extends (infer U)[] |
| 3 | ? readonly DeepReadonly<U>[] |
| 4 | : T[K] extends object |
| 5 | ? DeepReadonly<T[K]> |
| 6 | : T[K]; |
| 7 | }; |
Snippet 2 — uti
Practice snippet 2. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type UnionToIntersection<U> = |
| 2 | (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; |
| 3 | type I = UnionToIntersection<{ a: 1 } | { b: 2 }>; // { a:1 } & { b:2 } |
Snippet 3 — narrow-key
Practice snippet 3. Predict the resulting type, then confirm with hover types in your editor.
| 1 | function narrowKey<T extends object, K extends keyof T>( |
| 2 | obj: T, |
| 3 | key: K, |
| 4 | guard: (v: unknown) => boolean, |
| 5 | ): obj is T & Record<K, T[K]> { |
| 6 | return guard(obj[key]); |
| 7 | } |
Snippet 4 — as-tuple
Practice snippet 4. Predict the resulting type, then confirm with hover types in your editor.
| 1 | const asTuple = <const T extends readonly unknown[]>(xs: T) => xs; |
| 2 | const t = asTuple([1, "a", true]); |
Snippet 5 — json
Practice snippet 5. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type Json = |
| 2 | | string |
| 3 | | number |
| 4 | | boolean |
| 5 | | null |
| 6 | | Json[] |
| 7 | | { [key: string]: Json }; |
Snippet 6 — entries
Practice snippet 6. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type Entries<T> = { [K in keyof T]-?: [K, T[K]] }[keyof T]; |
| 2 | type E = Entries<{ a: 1; b: string }>; // ["a", 1] | ["b", string] |
Snippet 7 — atleast
Practice snippet 7. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type RequireAtLeastOne<T> = { |
| 2 | [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>; |
| 3 | }[keyof T]; |
| 4 | type Patches = RequireAtLeastOne<{ a?: number; b?: string }>; |
Snippet 8 — mutable-keys
Practice snippet 8. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type MutableKeys<T> = { |
| 2 | [K in keyof T]-?: Equal<{ -readonly [P in K]: T[K] }, { [P in K]: T[K] }> extends true ? K : never; |
| 3 | }[keyof T]; |
| 4 | type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false; |
Snippet 9 — snake
Practice snippet 9. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type Snake<S extends string> = S extends `${infer H}${infer T}` |
| 2 | ? T extends Uncapitalize<T> |
| 3 | ? `${Lowercase<H>}${Snake<T>}` |
| 4 | : `${Lowercase<H>}_${Snake<Uncapitalize<T>>}` |
| 5 | : S; |
Snippet 10 — publicof
Practice snippet 10. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type PublicOf<T> = { [K in keyof T]: T[K] }; |
| 2 | class Svc { #secret = 1; public open = true; } |
| 3 | type P = PublicOf<Svc>; |
note
Extended reference material for deliberate practice. Work through each snippet under strict: true and note where inference surprises you.
Snippet 1 — deep-readonly
Practice snippet 1. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type DeepReadonly<T> = { |
| 2 | readonly [K in keyof T]: T[K] extends (infer U)[] |
| 3 | ? readonly DeepReadonly<U>[] |
| 4 | : T[K] extends object |
| 5 | ? DeepReadonly<T[K]> |
| 6 | : T[K]; |
| 7 | }; |
Snippet 2 — uti
Practice snippet 2. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type UnionToIntersection<U> = |
| 2 | (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; |
| 3 | type I = UnionToIntersection<{ a: 1 } | { b: 2 }>; // { a:1 } & { b:2 } |
Snippet 3 — narrow-key
Practice snippet 3. Predict the resulting type, then confirm with hover types in your editor.
| 1 | function narrowKey<T extends object, K extends keyof T>( |
| 2 | obj: T, |
| 3 | key: K, |
| 4 | guard: (v: unknown) => boolean, |
| 5 | ): obj is T & Record<K, T[K]> { |
| 6 | return guard(obj[key]); |
| 7 | } |
Snippet 4 — as-tuple
Practice snippet 4. Predict the resulting type, then confirm with hover types in your editor.
| 1 | const asTuple = <const T extends readonly unknown[]>(xs: T) => xs; |
| 2 | const t = asTuple([1, "a", true]); |
Snippet 5 — json
Practice snippet 5. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type Json = |
| 2 | | string |
| 3 | | number |
| 4 | | boolean |
| 5 | | null |
| 6 | | Json[] |
| 7 | | { [key: string]: Json }; |
Snippet 6 — entries
Practice snippet 6. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type Entries<T> = { [K in keyof T]-?: [K, T[K]] }[keyof T]; |
| 2 | type E = Entries<{ a: 1; b: string }>; // ["a", 1] | ["b", string] |
Snippet 7 — atleast
Practice snippet 7. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type RequireAtLeastOne<T> = { |
| 2 | [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>; |
| 3 | }[keyof T]; |
| 4 | type Patches = RequireAtLeastOne<{ a?: number; b?: string }>; |
Snippet 8 — mutable-keys
Practice snippet 8. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type MutableKeys<T> = { |
| 2 | [K in keyof T]-?: Equal<{ -readonly [P in K]: T[K] }, { [P in K]: T[K] }> extends true ? K : never; |
| 3 | }[keyof T]; |
| 4 | type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false; |
Snippet 9 — snake
Practice snippet 9. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type Snake<S extends string> = S extends `${infer H}${infer T}` |
| 2 | ? T extends Uncapitalize<T> |
| 3 | ? `${Lowercase<H>}${Snake<T>}` |
| 4 | : `${Lowercase<H>}_${Snake<Uncapitalize<T>>}` |
| 5 | : S; |
Snippet 10 — publicof
Practice snippet 10. Predict the resulting type, then confirm with hover types in your editor.
| 1 | type PublicOf<T> = { [K in keyof T]: T[K] }; |
| 2 | class Svc { #secret = 1; public open = true; } |
| 3 | type P = PublicOf<Svc>; |
note
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.