Engineering Notes
Home/Languages/TypeScript/Utility Types
Languages · TypeScript

Utility Types

Utility types are generic types built into TypeScript's standard library that transform an existing type into a new, related type — making optional/required/readonly/subset variations of a type without redeclaring its shape from scratch. They are not special compiler syntax; they're ordinary generic types you could write…

💻 Languages● Intermediate⏱ 7 min read🛠️ 4 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • What Utility Types Are
  • Partial<T> — Every Property Optional
  • Required<T> — Every Property Mandatory
  • Readonly<T> — Every Property Read-Only
  • Pick<T, K> — Select a Subset of Properties
Part 1The Core Six

1What Utility Types Are

Utility types are generic types built into TypeScript's standard library that transform an existing type into a new, related type — making optional/required/readonly/subset variations of a type without redeclaring its shape from scratch. They are not special compiler syntax; they're ordinary generic types you could write yourself (full proof in "Under the Hood").

2Partial<T> — Every Property Optional

ts
interface User { id: number; name: string; email: string; }

function updateUser(id: number, changes: Partial<User>) {
  // changes might only include SOME of User's properties
}

updateUser(1, { name: "New Name" }); // ✅ — only name provided, rest omitted

Extremely common for "patch"-style update functions where callers only supply the fields they want to change.

3Required<T> — Every Property Mandatory

ts
interface Config { host?: string; port?: number; }

function connect(config: Required<Config>) {
  // both host and port are GUARANTEED present here
}

// connect({ host: "localhost" }); // ❌ port is still required now
connect({ host: "localhost", port: 8080 }); // ✅

The inverse of Partial — turns every optional property into a required one.

4Readonly<T> — Every Property Read-Only

ts
interface Point { x: number; y: number; }

const frozen: Readonly<Point> = { x: 1, y: 2 };
// frozen.x = 5; // ❌ error: x is readonly

Compile-time only, like the readonly modifier itself (see the Interfaces topic) — no runtime enforcement.

5Pick<T, K> — Select a Subset of Properties

ts
interface User { id: number; name: string; email: string; password: string; }

type PublicUser = Pick<User, "id" | "name">; // { id: number; name: string }

K must be a union of literal keys that actually exist on T — Pick<User, "id" | "typo"> would be a compile error.

Pick keeps only id and name from User; Omit keeps everything except password
Pick keeps only id and name from User; Omit keeps everything except password

6Omit<T, K> — Exclude a Subset of Properties

ts
type PublicUser = Omit<User, "password">; // everything EXCEPT password

The inverse of Pick — instead of listing what to keep, list what to remove.

7Record<K, V> — Build an Object Type From a Key Set and a Value Type

ts
type Role = "admin" | "editor" | "viewer";

type Permissions = Record<Role, boolean>;
// { admin: boolean; editor: boolean; viewer: boolean }

const perms: Permissions = { admin: true, editor: true, viewer: false };

Record is the standard way to build a dictionary-shaped type where every key from a known set maps to the same value type — far more precise than a generic index signature when the key set is closed and known.

8Composing Utility Types

ts
interface User { id: number; name: string; email: string; password: string; }

type UserUpdate = Partial<Omit<User, "id">>; // every field optional EXCEPT id is entirely excluded

Utility types compose naturally — this is one of their biggest strengths — because each one just takes a type and returns another type, so they nest exactly like function calls.

Part 2More Utilities and How They're Actually Built

1Exclude<T, U> — Remove Union Members

ts
type AllRoles = "admin" | "editor" | "viewer" | "banned";
type ActiveRoles = Exclude<AllRoles, "banned">; // "admin" | "editor" | "viewer"

Removes every member of the union T that is assignable to U.

2Extract<T, U> — Keep Only Matching Union Members

ts
type Mixed = string | number | (() => void);
type OnlyFunctions = Extract<Mixed, Function>; // () => void

The inverse of Exclude — keeps only the members of T that ARE assignable to U.

3NonNullable<T> — Strip null and undefined

ts
type MaybeName = string | null | undefined;
type DefiniteName = NonNullable<MaybeName>; // string

4ReturnType<T> and Parameters<T> — Extract Function Signature Pieces

ts
function createUser(name: string, age: number) {
  return { id: 1, name, age };
}

type User = ReturnType<typeof createUser>;    // { id: number; name: string; age: number }
type CreateUserArgs = Parameters<typeof createUser>; // [name: string, age: number]

Extremely useful for deriving types from existing functions instead of duplicating a shape by hand — if createUser's implementation changes, User and CreateUserArgs stay automatically correct.

5Awaited<T> — Unwrap a Promise Type

ts
async function fetchUser() {
  return { id: 1, name: "Ana" };
}

type FetchedUser = Awaited<ReturnType<typeof fetchUser>>; // { id: number; name: string }
// without Awaited, ReturnType<typeof fetchUser> would be Promise<{ id: number; name: string }>
Under the HoodUtility Types Are Just Ordinary Type Definitions▾

None of these are special compiler syntax — every single one is defined in TypeScript's own lib.es5.d.ts (and related lib files), shipped as part of the compiler, using the exact same mapped-type and conditional-type features covered in their own topics. You could write every one of these yourself, and in fact this is genuinely their real source:

ts
type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Required<T> = {
  [P in keyof T]-?: T[P];
};

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

type Exclude<T, U> = T extends U ? never : T;

type Extract<T, U> = T extends U ? T : never;

type NonNullable<T> = T extends null | undefined ? never : T;

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
Utility types like Partial and Pick are ordinary mapped/conditional types defined in TypeScript's own lib.es5.d.ts, not compiler built-ins
Utility types like Partial and Pick are ordinary mapped/conditional types defined in TypeScript's own lib.es5.d.ts, not compiler built-ins

Reading Partial<T> for real, now that the pieces are covered elsewhere

[P in keyof T]?: T[P] is a mapped type (its own topic): keyof T gets every property name of T as a union of literal types, P in <that union> iterates each one, T[P] looks up that property's original type (an indexed access type), and the ? marks each resulting property optional. Nothing here is compiler magic — it's the mapped-types feature, applied once, in the standard library, and given a convenient name.

Required<T>'s -? is a mapped-type modifier, not a typo

The -? you see in the real Required<T> definition removes the optional modifier from every property (as opposed to plain ? which adds it) — full detail on +/- modifiers belongs to the Mapped Types topic, but it's worth seeing here as direct proof that Required is Partial's exact structural opposite, implemented with one symbol's difference.

Why this matters practically

Because utility types are ordinary type definitions, you can read their real source directly in your editor (jump-to-definition on Partial in almost any TypeScript-aware editor lands you in lib.es5.d.ts), and — more importantly — you can write your own domain-specific equivalents using the exact same techniques once you've covered Mapped Types, Conditional Types, and the infer keyword. DeepPartial<T>, DeepReadonly<T>, and similar community-standard "missing" utility types that aren't built in are written using precisely this same toolkit.

⚠️ Common Pitfalls

  1. Assuming Partial/Required/Readonly are deep — all three are shallow: Partial<{ a: { b: number } }> makes a optional, but a's nested b is still required; deep variants need custom recursive mapped types (own construction, or a library like type-fest).
  2. Confusing Exclude/Extract direction — Exclude removes matches, Extract keeps matches; easy to mix up since both take the same two type arguments.
  3. Using Pick/Omit with a key that doesn't exist on T — Pick errors immediately (its K extends keyof T constraint catches it); Omit historically did NOT have this constraint for backward-compatibility reasons in some TS versions, so a typo'd key can silently do nothing instead of erroring — always double check Omit key names carefully.
  4. Reaching for ReturnType/Parameters on an overloaded function — they only reflect the LAST overload signature by default, which can be surprising for heavily overloaded functions.

📋 Quick Reference

text
Partial<T>  / Required<T>  / Readonly<T>     → shallow, per-property modifier changes
Pick<T,K>   / Omit<T,K>                        → subset selection
Record<K,V>                                     → build a dictionary shape
Exclude<T,U> / Extract<T,U>                     → union filtering (remove / keep matches)
NonNullable<T>                                   → strip null | undefined
ReturnType<T> / Parameters<T> / Awaited<T>        → derive types FROM existing functions/promises

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1Name common utility types and their uses.core▾

Partial<T> (all optional, e.g. update DTOs), Required<T>, Readonly<T>, Pick<T, K>, Omit<T, K>, Record<K, V>, Exclude<U, X>, Extract<U, X>, NonNullable<T>, ReturnType<F>, Parameters<F>, Awaited<T>.

Follow-ups interviewers ask
  • Pick vs Omit? Pick selects keys; Omit removes them.
Q2How would you type a PATCH request body?core▾

Partial<Pick<User, 'name' | 'email'>> limits updatable fields and makes them optional.

Follow-ups interviewers ask
  • Why not Partial<User>? It would allow updating id, role, etc.
Q3How are utility types implemented?core▾

Mostly with mapped and conditional types: type Partial<T> = { [K in keyof T]?: T[K] }, type Exclude<T, U> = T extends U ? never : T.

Follow-ups interviewers ask
  • Implement ReturnType? T extends (...args: any) => infer R ? R : never.
Q4What's the pitfall of Omit with unions?senior▾

Omit isn't distributive: on a union it collapses to common keys, losing discriminants. Use a distributive version: type DistributiveOmit<T, K> = T extends any ? Omit<T, K> : never.

Follow-ups interviewers ask
  • Why? keyof a union gives only common keys.
Q5How do Awaited and ReturnType help with async functions?core▾

Awaited<ReturnType<typeof fetchUser>> gives the resolved type of an async function's result, avoiding duplicated type definitions.

Follow-ups interviewers ask
  • Parameters example? Parameters<typeof fn>[0] for the first argument type.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.