skip to content

Built-in Utility Types

The utility types that ship in lib.es5.d.ts and show up in almost every real codebase. Interviewers ask about them because knowing which one to reach for — and where each one is shallow or unchecked — is the everyday half of type-level work.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

23

In TypeScript, what do the built-in utility types `Exclude<T, U>`, `Extract<T, U>` and `NonNullable<T>` produce when you apply them to a union type?

level: juniorimportance: must knowfreq 72%

answer

  1. set operations over union members
  2. difference, intersection, drop nullish
  3. assignability decides, not equality
  4. rejected members become never
  5. erased — no runtime filtering

basics

~10 s

They are set operations over unions. Exclude<T, U> drops every member of T assignable to U, Extract<T, U> keeps only those members, and NonNullable<T> removes null and undefined from T.

solid answer

~40 s

All three filter the members of a union at compile time. `Exclude<T, U>` walks the union `T` member by member and throws away any member assignable to `U`, so `Exclude<'draft' | 'live' | 'archived', 'archived'>` is `'draft' | 'live'`. `Extract<T, U>` is the mirror image — it keeps only the members assignable to `U`, so `Extract<string | number | boolean, string>` is `string`. `NonNullable<T>` is the special case everyone reaches for under `strictNullChecks`: it strips `null` and `undefined`, so `NonNullable<User | null | undefined>` is `User`. Members that filter out contribute `never`, and `never` vanishes inside a union, which is why the result is just the surviving members. If nothing survives, the whole result is `never`. All of this is erased at compile time — no runtime filtering happens.

code

typescript · 13 lines
typescript
type Status = 'draft' | 'live' | 'archived';

type Editable = Exclude<Status, 'archived'>; // 'draft' | 'live'
type Frozen = Extract<Status, 'archived' | 'deleted'>; // 'archived'
type Loaded = NonNullable<{ id: string } | null | undefined>; // { id: string }

// A non-union type behaves as a one-member union:
type Wide = Exclude<string, 'a'>; // string, not "string without 'a'"
type Narrow = Extract<string, 'a'>; // never — string is not assignable to 'a'

declare function render(status: Editable): string;
render('draft'); // ok
// render('archived'); // Error: 'archived' is not assignable to 'draft' | 'live'

go deeper

for a junior

Be able to state plainly what each of the three does to a union and give a one-line example, such as Exclude of a status union dropping 'archived'. Say clearly that they operate on union members.

for a middle

Explain that the test is assignability rather than equality, that rejected members become never and disappear from the union, and that a fully filtered union collapses to never.

for a senior

Show where these earn their keep in a codebase: deriving narrow parameter types from a single source-of-truth union so call sites fail early, and knowing that NonNullable is a claim to the checker, not a runtime guard.

for a principal

Own the modelling question of whether a derived union should be computed with these operators or declared explicitly. Derivation keeps one source of truth but makes errors surface far from the declaration, so weigh readability against drift.

## The mental model: unions are sets A union type like `'draft' | 'live' | 'archived'` behaves like a set of three possibilities. `Exclude`, `Extract` and `NonNullable` are the set operations over that set: difference, intersection, and "remove the two nullish members". They live in TypeScript's standard library (`lib.es5.d.ts`), so they are always available without an import, and like everything in the type layer they exist only during compilation — the emitted JavaScript contains nothing from them. ## Exclude<T, U> — set difference `Exclude<T, U>` keeps the members of `T` that are **not** assignable to `U`: ```ts type Status = 'draft' | 'live' | 'archived'; type Editable = Exclude<Status, 'archived'>; // 'draft' | 'live' type NotFn = Exclude<string | number | (() => void), Function>; // string | number ``` The test is *assignability*, not string equality. `Exclude<string | number | (() => void), Function>` removes the function member because a `() => void` is assignable to `Function`. That also means excluding a supertype removes everything under it. One consequence surprises people: the filter is applied to each **member** of a union, and a non-union type is treated as a one-member union. So `Exclude<string, 'a'>` is `string` — `string` is not assignable to the literal `'a'`, so nothing is removed. It does *not* mean "string minus the letter a"; there is no such type. ## Extract<T, U> — set intersection `Extract<T, U>` is the complement: it keeps exactly the members `Exclude` would have thrown away. ```ts type Primitives = string | number | boolean | null; type Textish = Extract<Primitives, string | number>; // string | number type Nope = Extract<string, 'a'>; // never — string is not assignable to 'a' ``` The last line is the mirror image of the `Exclude<string, 'a'>` case and catches candidates out: direction matters. `Extract<'a' | 'b', string>` is `'a' | 'b'` because each literal *is* assignable to `string`, but `Extract<string, 'a'>` is `never` because the wide `string` is not assignable to the narrow `'a'`. ## NonNullable<T> — drop null and undefined ```ts type MaybeUser = { id: string } | null | undefined; type User = NonNullable<MaybeUser>; // { id: string } ``` This matters only when `strictNullChecks` is on; without it, `null` and `undefined` are already absorbed into every type and there is nothing to strip. In current TypeScript (the 5.x line) the standard library defines it as an intersection, `type NonNullable<T> = T & {}`, because the type `{}` means "any value except `null` and `undefined`". The observable behaviour is the same as removing the nullish members. ## Why the results look the way they do When a member fails the filter it becomes `never`, the empty type. `never` is the identity element of a union: `string | never` reduces to `string`. So filtering three members and rejecting one leaves `'draft' | 'live' | never`, which the checker immediately reduces to `'draft' | 'live'`. If **every** member is rejected you are left with `never` alone, and `never` has no values — the first assignment downstream will fail with a confusing error, which is usually the sign that a filter was too aggressive or a literal was misspelled. ## Where you actually use them - Deriving a narrower state union from a wider one: `type OpenTicket = Exclude<TicketState, 'closed' | 'archived'>`. - Pulling one variant out of a tagged union: `Extract<Action, { type: 'add' }>`. - Cleaning up an optional lookup result so downstream code is not forced to re-check for `null`. - Narrowing a callback parameter type after you have already validated it. ```ts function render(status: Exclude<Status, 'archived'>): string { return status === 'draft' ? 'Draft' : 'Live'; } ``` The compiler now rejects `render('archived')` at the call site, and the `switch` inside needs only two branches. ## The runtime boundary None of these does any filtering at run time. `NonNullable<T>` does not check for `null`; it only tells the checker that you have already ruled it out. If the value can actually be `null` at run time, you still need a real check — the utility type just stops the compiler from complaining, which is exactly the failure mode to avoid.

  • What does `Exclude<string, 'a'>` evaluate to, and why does that surprise people?
    It evaluates to `string`. A non-union type is treated as a single-member union, and `string` is not assignable to the literal `'a'`, so nothing is removed. People read it as "string without the letter a", but there is no such type — `Exclude` only ever removes whole union members.
  • What happens if every member of the union is filtered out?
    You get `never`. Each rejected member contributes `never`, and a union of nothing but `never` reduces to `never`. Nothing is assignable to `never`, so the first place you try to use the type produces an error that looks unrelated to the filter — usually the sign of a too-broad `U` or a misspelled literal.
  • Does `NonNullable<T>` do anything if `strictNullChecks` is off?
    Effectively no. Without `strictNullChecks`, `null` and `undefined` are assignable to almost every type and are not tracked as separate union members, so there is nothing for it to strip. The utility is only meaningful in a strict codebase, which is one more argument for turning the flag on.

saying these in an interview costs you the question

  • Thinking Exclude removes characters or keys, not union members
  • Believing NonNullable adds a runtime null check
  • Assuming Extract<string, 'a'> gives 'a' rather than never
  • Saying these need an import from a library
  • Expecting an error when the filtered type is unrelated

context

open as a page

In TypeScript, a function `createUser` returns an object literal whose shape you never declared. How do you get a named type for that return value without writing the shape twice, and why does `ReturnType<createUser>` fail to compile?

level: juniorimportance: must knowfreq 72%

basics

~10 s

ReturnType<typeof createUser> gives the type. In a type position, typeof turns the value createUser into its function type, which ReturnType then unwraps. ReturnType<createUser> fails because createUser is a value, not a type.

open as a page

In TypeScript, what does each of the built-in utility types `Partial<T>`, `Required<T>` and `Readonly<T>` do to the properties of T?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Partial<T> makes every property of T optional, Required<T> makes every property mandatory, and Readonly<T> makes every property read-only. All three keep T's keys and property types, flip one modifier, and disappear when the code is compiled to JavaScript.

open as a page

In TypeScript, what do `Pick<T, K>` and `Omit<T, K>` produce, and why derive a type with them instead of hand-writing a second interface?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Pick<T, K> builds an object type containing only the properties of T named in K; Omit<T, K> contains every property of T except those. Deriving keeps one source of truth, so editing T updates both types automatically.

open as a page

In TypeScript, what object type does the built-in `Record<K, V>` utility produce, and how does its mapped-type definition explain the difference between `Record<string, number>` and `Record<'a' | 'b', number>`?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Record<K, V> builds an object type whose keys come from K and whose values are all V. It is defined as the mapped type { [P in K]: V }, so a string key produces an open index signature while a union of literals produces exactly those required properties.

open as a page

Write the one-line definitions of TypeScript's built-in `Exclude<T, U>` and `Extract<T, U>` yourself, and explain why they filter a union member by member instead of testing the union as a whole.

level: middleimportance: must knowfreq 62%

basics

~20 s

Exclude<T, U> is T extends U ? never : T and Extract<T, U> is T extends U ? T : never. Because T is a bare type parameter, the conditional is applied to each union member separately and the results are unioned.

open as a page

In TypeScript, why does `ReturnType<typeof fetchUser>` not give you the user object when `fetchUser` is an `async` function, and what does `Awaited<T>` do about it?

level: middleimportance: must knowfreq 58%

basics

~10 s

An async function's return type is Promise<T>, so ReturnType gives Promise<{...}>, not the object. Awaited<T> unwraps it: Awaited<ReturnType<typeof fetchUser>> is the user shape. Awaited recurses through nested promises and passes non-promise types through unchanged.

open as a page

In TypeScript, given `type Config = { name: string; db: { host: string; port: number } }`, what exactly does `Partial<Config>` make optional, and why does `cfg.db.host = "x"` still compile when `cfg` is typed `Readonly<Config>`?

level: middleimportance: must knowfreq 58%

basics

~20 s

Both utilities map only over Config's top-level keys. Partial<Config> makes name and db optional but leaves host and port inside db required, and Readonly<Config> marks the db property read-only without touching the object it points to.

open as a page

In TypeScript, why does `Omit<User, "nmae">` compile without complaint when `User` has no `nmae` property, while `Pick<User, "nmae">` is a compile error?

level: middleimportance: must knowfreq 60%

basics

~20 s

Their key parameters are constrained differently. Pick declares K extends keyof T, so a typo fails the constraint. Omit declares K extends keyof any, accepting any property name, then subtracts it from T's keys — subtracting a key that is absent simply removes nothing.

open as a page

In TypeScript, why does a lookup object typed `Record<Status, string>` — where `Status` is a union of string literals — start failing to compile when a new member is added to `Status`, while `Record<string, string>` keeps compiling?

level: middleimportance: must knowfreq 62%

basics

~20 s

A union of literals is a finite key set, so Record generates one required property per member. Adding a member generates one more required property, and every existing lookup object is now missing it. Record<string, string> is an open index signature that requires no key at all, so nothing can be missing.

open as a page

A TypeScript service reads `const rate = rates[currency]` from a value typed `Record<string, number>` and then crashes on `rate.toFixed(2)`. Why did the compiler allow that, and how would you type the lookup table so it cannot happen?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Record<string, number> declares an open key set, so the compiler types every lookup as number whether or not the key exists — it models permitted keys, not present ones. Model absence explicitly instead: Record<string, number | undefined>, or a Partial Record over a finite key union.

open as a page

Given `type Action = { type: 'add'; payload: number } | { type: 'remove'; id: string } | { type: 'reset' }`, how do you write a TypeScript type for just the `'add'` variant — and for its payload — without repeating the shape?

level: middleimportance: should knowfreq 52%

basics

~20 s

Use Extract with a partial shape as the filter: Extract<Action, { type: 'add' }> is the add variant, and Extract<Action, { type: 'add' }>['payload'] is its payload type. Nothing is duplicated, so the types follow the union.

open as a page

In TypeScript, given `class HttpClient { constructor(baseUrl: string, timeoutMs: number) {} }`, how do you derive a tuple of its constructor arguments and its instance type, and why does `InstanceType<HttpClient>` fail to compile?

level: middleimportance: should knowfreq 42%

basics

~10 s

Use ConstructorParameters<typeof HttpClient> for the argument tuple and InstanceType<typeof HttpClient> for the instance. InstanceType<HttpClient> fails because the bare class name already means the instance type; the constructor side is reached only through typeof HttpClient.

open as a page

In TypeScript, you are wrapping an existing `sendEmail(to: string, subject: string, body: string): boolean` with a logging version. How do you type the wrapper so its parameters stay in sync with `sendEmail`, and what exactly is the type `Parameters<typeof sendEmail>`?

level: middleimportance: should knowfreq 55%

basics

~10 s

Declare the wrapper as (...args: Parameters<typeof sendEmail>) and spread args into the call. Parameters<typeof sendEmail> is the labelled tuple [to: string, subject: string, body: string], so adding or reordering parameters updates the wrapper automatically.

open as a page

In TypeScript, how does typing a value as `Readonly<T>` differ from writing `as const` on the object literal?

level: middleimportance: should knowfreq 50%

basics

~20 s

Readonly<T> is a type operator applied to an existing type: it marks the top-level properties read-only and leaves the property types alone. A const assertion applies to a literal expression, infers the narrowest literal types, and makes the whole literal deeply read-only.

open as a page

In TypeScript, `Record<string, User>` and `{ [key: string]: User }` describe the same dictionary — so what do the two spellings actually differ in, and when would you reach for `Record`?

level: middleimportance: should knowfreq 52%

basics

~20 s

For a string key they produce the identical type — Record<string, User> expands to that index signature. They differ as tools: Record is a generic alias whose key set can be a union, enum or computed type and which composes with other utilities, while an index signature is a declaration form that can sit inside an interface next to named members.

open as a page

In TypeScript, `type Status = 'draft' | 'live' | 'archived'` and someone writes `Exclude<Status, 'archivd'>` with a typo. Why does that compile without any error, what does it evaluate to, and how would you make such a filter fail loudly?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Exclude puts no constraint on its second type argument, so any type is a legal filter. The misspelled literal matches no member, nothing is removed, and the result is the full Status union. Constrain the filter yourself with a helper such as type ExcludeStrict<T, U extends T> = Exclude<T, U>.

open as a page

In TypeScript, what do `ReturnType` and `Parameters` give you when applied to an overloaded function and to a generic function, and why does that break a wrapper built on them?

level: seniorimportance: should knowfreq 34%

basics

~20 s

For an overloaded function both utilities see only the last overload signature, silently discarding the others. For a generic function the type parameters are already instantiated — unconstrained ones become unknown — so the caller's argument type never reaches the return type.

open as a page

A TypeScript update endpoint types its request body as `Partial<User>` and applies it with `return { ...user, ...patch }`. Which update bugs does that typing still permit?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Partial<User> permits every field to be patched, including identity fields that must never change; it accepts an empty object; and it accepts an explicit undefined for any property, which the spread then copies over a real value, leaving a User whose required field is undefined while the type still claims it is present.

open as a page

You apply `Omit<Shape, "id">` in TypeScript where `Shape` is a union of object types, and the result collapses into a single flat type instead of a union. Why, and how do you keep the variants?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Omit applies to the union as a whole, not member by member. It computes keyof Shape, which is only the keys every member shares, then picks from that — so member-specific properties vanish. A distributive wrapper that maps Omit over each member preserves the variants.

open as a page

When is deriving an API response type with `Omit` from a database entity type the wrong call in TypeScript, and what would you do instead?

level: principalimportance: should knowfreq 34%

basics

~20 s

Deriving is wrong when the two types are separate contracts that merely look alike. Omit couples your wire format to storage, fails open when a field is renamed, and never errors on a stale key — so a public contract is better declared explicitly and checked against the entity.

open as a page

In TypeScript, what are `Required<{ a?: number }>` and `Required<{ b: number | undefined }>`, and why do the two results differ?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Required<{ a?: number }> is { a: number } and Required<{ b: number | undefined }> is unchanged at { b: number | undefined }. Required removes the optional marker, and only where it removes one does it also drop undefined from that property's type.

open as a page

In TypeScript, when should a shared type be declared explicitly and enforced onto the implementation, rather than derived from it with `ReturnType<typeof ...>`?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Declare the type wherever it is a contract others depend on: a package's public API, a cross-team boundary, a persisted or wire shape. Derive inside a module where the implementation is genuinely the source of truth, such as store state or internal factory results.

open as a page