Master TypeScript's built-in utility types — Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, ReturnType, and more. Learn what each does, when to use it, and how to implement them yourself using mapped and conditional types.
TypeScript's utility types are a standard library for type transformations — the same idea as lodash for runtime values, but for types at compile time. Just as lodash.pick(obj, keys) extracts a subset of an object's properties at runtime, Pick<User, 'id' | 'name'> extracts a subset of User's type definition at compile time. Each utility type is a generic that takes one or more types and produces a transformed type — and every one of them is implemented using the same mapped and conditional type primitives you can write yourself.
// Partial<T> — makes every property optional
interface User { id: number; name: string; email: string; }
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }
// Essential for update/patch functions:
function updateUser(id: number, changes: Partial<User>): User { /* ... */ }
updateUser(1, { name: "Bob" }); // Only name — perfectly valid
// Required<T> — makes every property required (removes ?)
interface Config { host?: string; port?: number; debug?: boolean; }
type StrictConfig = Required<Config>;
// { host: string; port: number; debug: boolean; }
// Readonly<T> — makes every property readonly
const config: Readonly<Config> = { host: "localhost", port: 3000 };
config.host = "example.com"; // Error: Cannot assign to 'host' — readonly
interface Product {
id: number;
title: string;
description: string;
price: number;
internalCode: string; // sensitive — don't expose to client
}
// Pick — include only listed keys
type ProductSummary = Pick<Product, "id" | "title" | "price">;
// { id: number; title: string; price: number; }
// Omit — exclude listed keys (dual of Pick)
type PublicProduct = Omit<Product, "internalCode">;
// { id: number; title: string; description: string; price: number; }
// Prefer Omit when you want to exclude a few sensitive fields from a large type
// Prefer Pick when you want to select a small subset from a large type
// Record<K, V> — a type with keys K and values V
type UserMap = Record<string, User>;
// { [key: string]: User }
// More precise — limit the keys to a union
type Permission = "read" | "write" | "delete";
type RolePermissions = Record<Permission, boolean>;
// { read: boolean; write: boolean; delete: boolean; }
// TypeScript will error if you miss a key — exhaustive check
const adminPerms: RolePermissions = { read: true, write: true, delete: true };
const guestPerms: RolePermissions = { read: true, write: false }; // Error: missing 'delete'
type AllStatus = "active" | "inactive" | "deleted" | "banned";
// Exclude removes matching members from a union
type VisibleStatus = Exclude<AllStatus, "deleted" | "banned">;
// "active" | "inactive"
// Extract keeps only matching members
type ProblematicStatus = Extract<AllStatus, "deleted" | "banned">;
// "deleted" | "banned"
// NonNullable removes null and undefined
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
// Practical use: filtering a union to only object types
type OnlyFunctions = Extract<string | number | (() => void), Function>;
// () => void
function createUser(name: string, age: number): User { /* ... */ }
// ReturnType — extracts the return type of a function
type CreatedUser = ReturnType<typeof createUser>;
// User
// Parameters — extracts a tuple of the parameter types
type CreateUserArgs = Parameters<typeof createUser>;
// [name: string, age: number]
// InstanceType — extracts the instance type of a constructor
class UserService { getUser(id: number): User { /* ... */ } }
type UserServiceInstance = InstanceType<typeof UserService>;
// UserService
// These are invaluable when you don't control the source types —
// you can derive types from functions/classes you import from libraries
Every built-in utility type is implemented using mapped or conditional types. Understanding the implementations is a strong interview signal:
// How Partial is implemented in TypeScript's lib.d.ts
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
// How Required is implemented
type MyRequired<T> = {
[P in keyof T]-?: T[P]; // -? removes the optional modifier
};
// How Readonly is implemented
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
// How Pick is implemented
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// How Record is implemented
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};
// How Exclude is implemented (conditional type distributing over union)
type MyExclude<T, U> = T extends U ? never : T;Many developers think Partial<T> makes all nested properties optional recursively — it only makes the top-level properties optional. Nested objects remain fully required. You need a custom DeepPartial type for recursive optionality.
Many developers confuse Omit and Exclude — Omit works on object types and removes properties by key name. Exclude works on union types and removes members by type. They are solving different problems.
Many developers think Record<string, T> and { [key: string]: T } are different types — they produce identical index signatures. Record is preferred for readability and for using union keys, which the index signature syntax cannot express.
Many developers think ReturnType<T> requires the actual function — it requires typeof theFunction. ReturnType<typeof createUser> not ReturnType<createUser>.
Many developers think utility types are magic compiler features — every one is implemented using mapped types and conditional types that you can write yourself. The TypeScript source for all built-in utilities is in lib.es5.d.ts and is well worth reading.
Many developers reach for Partial on function parameters instead of using optional properties — Partial<Options> as a parameter type is correct. But for function return types, Partial can hide missing data that callers expect to be present.
PATCH API endpoints: function patchUser(id: string, data: Partial<User>) is the idiomatic pattern for update operations where only changed fields are sent — Partial makes all fields optional without creating a separate PartialUser interface.
Config objects with defaults: Required<AppConfig> as the internal type after merging with defaults ensures every optional config option has been resolved before it reaches internal code — a clean way to enforce completeness after a merge step.
Permission maps: Record<Permission, boolean> for role-based access control ensures every permission key is explicitly set when creating a role object, giving exhaustive compile-time checking that a plain object literal would not provide.
Deriving types from imported libraries: ReturnType<typeof libraryFunction> and Parameters<typeof libraryFunction> are essential when a library doesn't export its types directly — you can derive what you need from the function signatures themselves.
React component prop subsets: Pick<ButtonProps, 'onClick' | 'disabled' | 'children'> creates a minimal prop interface for a wrapper component without manually duplicating individual props — stays in sync automatically when ButtonProps changes.
Form state types: Partial<FormValues> for draft state that is progressively filled in, transitioning to FormValues (fully required) only on submit — models the two states with type-level precision.
No questions tagged to this topic yet.
Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.