TypeScript — Utility Types
TypeScript ships with a comprehensive set of built-in utility types that transform existing types into new ones. These utilities eliminate endless boilerplate and let you express complex type relationships in a single line. They operate at the type level only — zero runtime cost.
Utility types are the foundation of productive TypeScript. Rather than manually writing mapped types and conditional types for common patterns, you compose these building blocks. Mastering them will cut your type definitions by half.
note
Partial<T> makes every property optional. Required<T> makes every property required, removing optional modifiers. These are inverses: Required<Partial<T>> restores the original.
| 1 | interface User { |
| 2 | id: number; |
| 3 | name: string; |
| 4 | email: string; |
| 5 | role: "admin" | "user"; |
| 6 | } |
| 7 | |
| 8 | // Partial — all properties become optional |
| 9 | type PartialUser = Partial<User>; |
| 10 | // { id?: number; name?: string; email?: string; role?: "admin" | "user" } |
| 11 | |
| 12 | function updateUser(id: number, updates: Partial<User>): void { |
| 13 | // Only pass what changed |
| 14 | } |
| 15 | |
| 16 | updateUser(1, { name: "Alice" }); // ok |
| 17 | updateUser(1, {}); // ok — empty partial |
| 18 | |
| 19 | // Required — all properties become mandatory (removes ?) |
| 20 | type RequiredUser = Required<PartialUser>; |
| 21 | // { id: number; name: string; email: string; role: "admin" | "user" } |
| 22 | |
| 23 | // Practical: update API with partial payload |
| 24 | function patchUser(id: number, fields: Partial<User>): Promise<User> { |
| 25 | return fetch(`/api/users/${id}`, { |
| 26 | method: "PATCH", |
| 27 | body: JSON.stringify(fields), |
| 28 | }).then((r) => r.json()); |
| 29 | } |
info
Readonly<T> adds the readonly modifier to every property, making the type deeply immutability-safe at the type level (shallow only).
| 1 | interface Config { |
| 2 | host: string; |
| 3 | port: number; |
| 4 | timeout: number; |
| 5 | } |
| 6 | |
| 7 | type ImmutableConfig = Readonly<Config>; |
| 8 | // { readonly host: string; readonly port: number; readonly timeout: number } |
| 9 | |
| 10 | const config: ImmutableConfig = { |
| 11 | host: "localhost", |
| 12 | port: 8080, |
| 13 | timeout: 5000, |
| 14 | }; |
| 15 | |
| 16 | // config.host = "other"; // Error: cannot assign to readonly property |
| 17 | |
| 18 | // Shallow — nested objects are still mutable |
| 19 | interface AppConfig { |
| 20 | database: { host: string; port: number }; |
| 21 | } |
| 22 | |
| 23 | type ShallowReadonly = Readonly<AppConfig>; |
| 24 | // database is still mutable! Only the outer .database ref is readonly |
| 25 | |
| 26 | // Deep readonly — combine with recursive conditional types (covered in advanced) |
| 27 | type DeepReadonly<T> = { |
| 28 | readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]; |
| 29 | }; |
| 30 | |
| 31 | // Use case: frozen configuration objects |
| 32 | function freezeConfig<T extends object>(cfg: T): Readonly<T> { |
| 33 | return Object.freeze(cfg) as Readonly<T>; |
| 34 | } |
best practice
Pick<T, K> selects a subset of properties from T. Omit<T, K> removes properties. Together they let you derive new types from existing ones without duplication.
| 1 | interface Product { |
| 2 | id: number; |
| 3 | name: string; |
| 4 | price: number; |
| 5 | description: string; |
| 6 | category: string; |
| 7 | createdAt: Date; |
| 8 | updatedAt: Date; |
| 9 | } |
| 10 | |
| 11 | // Pick — select specific keys |
| 12 | type ProductPreview = Pick<Product, "id" | "name" | "price">; |
| 13 | // { id: number; name: string; price: number } |
| 14 | |
| 15 | // Omit — remove specific keys |
| 16 | type ProductInput = Omit<Product, "id" | "createdAt" | "updatedAt">; |
| 17 | // { name: string; price: number; description: string; category: string } |
| 18 | |
| 19 | // Pick with computed keys |
| 20 | type Keys = "a" | "b"; |
| 21 | type Picked = Pick<{ a: 1; b: 2; c: 3 }, Keys>; |
| 22 | // { a: 1; b: 2 } |
| 23 | |
| 24 | // Omit is implemented as Pick<T, Exclude<keyof T, K>> |
| 25 | // You can think of it as "everything except these keys" |
| 26 | |
| 27 | // Practical: API response vs request types |
| 28 | interface APIResponse<T> { |
| 29 | data: T; |
| 30 | status: number; |
| 31 | message: string; |
| 32 | timestamp: string; |
| 33 | } |
| 34 | |
| 35 | // Client only cares about data and status |
| 36 | type ClientResponse<T> = Pick<APIResponse<T>, "data" | "status">; |
| 37 | |
| 38 | // Create without timestamps |
| 39 | type CreateProduct = Omit<Product, "createdAt" | "updatedAt"> & { |
| 40 | tags: string[]; |
| 41 | }; |
| 1 | // Pick and Omit work with union types too |
| 2 | type Event = |
| 3 | | { type: "click"; x: number; y: number } |
| 4 | | { type: "keypress"; key: string } |
| 5 | | { type: "focus"; element: HTMLElement }; |
| 6 | |
| 7 | // Pick discriminated unions by their 'type' field |
| 8 | type ClickEvent = Extract<Event, { type: "click" }>; |
| 9 | // { type: "click"; x: number; y: number } |
| 10 | |
| 11 | // Omit can remove discriminated branches |
| 12 | type NonKeyboardEvents = Omit<Event, { type: "keypress" }>; |
| 13 | // This does NOT work as expected with unions! |
| 14 | |
| 15 | // Correct approach — use Exclude with Extract: |
| 16 | type NonKeyboardEvents2 = Exclude<Event, { type: "keypress" }>; |
| 17 | // { type: "click"; ... } | { type: "focus"; ... } |
info
Record<K, V> constructs an object type whose keys are K and values are V. It is the go-to utility for dictionary-like structures, enums-as-maps, and lookup tables.
| 1 | // Basic — map string keys to number values |
| 2 | type PageViews = Record<string, number>; |
| 3 | const views: PageViews = { "/": 100, "/about": 50, "/contact": 25 }; |
| 4 | |
| 5 | // Constrain keys to a union |
| 6 | type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"; |
| 7 | type Handler = () => void; |
| 8 | |
| 9 | type RouteHandlers = Record<HttpMethod, Handler>; |
| 10 | const handlers: RouteHandlers = { |
| 11 | GET: () => console.log("get"), |
| 12 | POST: () => console.log("post"), |
| 13 | PUT: () => console.log("put"), |
| 14 | DELETE: () => console.log("delete"), |
| 15 | }; |
| 16 | |
| 17 | // Record with complex value types |
| 18 | type UserRole = "admin" | "editor" | "viewer"; |
| 19 | type Permissions = Record<UserRole, string[]>; |
| 20 | |
| 21 | const rolePermissions: Permissions = { |
| 22 | admin: ["create", "read", "update", "delete"], |
| 23 | editor: ["create", "read", "update"], |
| 24 | viewer: ["read"], |
| 25 | }; |
| 26 | |
| 27 | // Nested records |
| 28 | type Config = Record<string, Record<string, string | number>>; |
| 29 | const appConfig: Config = { |
| 30 | database: { host: "localhost", port: 5432 }, |
| 31 | cache: { host: "localhost", port: 6379 }, |
| 32 | }; |
| 33 | |
| 34 | // Record with optional-like behavior via Partial |
| 35 | type PartialPermissions = Partial<Record<UserRole, string[]>>; |
| 36 | // { admin?: string[]; editor?: string[]; viewer?: string[] } |
| 37 | |
| 38 | // Record for enum-like mapping |
| 39 | enum Color { |
| 40 | Red = "RED", |
| 41 | Green = "GREEN", |
| 42 | Blue = "BLUE", |
| 43 | } |
| 44 | |
| 45 | type ColorMap = Record<Color, string>; |
| 46 | const hexColors: ColorMap = { |
| 47 | [Color.Red]: "#ff0000", |
| 48 | [Color.Green]: "#00ff00", |
| 49 | [Color.Blue]: "#0000ff", |
| 50 | }; |
best practice
Exclude<T, U> removes from T any members assignable to U. Extract<T, U> keeps only members assignable to U. These operate on union types primarily.
| 1 | // Exclude — remove types from a union |
| 2 | type AllTypes = string | number | boolean | null | undefined; |
| 3 | type PrimitiveOnly = Exclude<AllTypes, null | undefined>; |
| 4 | // string | number | boolean |
| 5 | |
| 6 | type WithoutString = Exclude<string | number | boolean, string>; |
| 7 | // number | boolean |
| 8 | |
| 9 | // Extract — keep only matching types |
| 10 | type NumbersAndStrings = string | number | boolean | null; |
| 11 | type JustStrings = Extract<NumbersAndStrings, string>; |
| 12 | // string |
| 13 | |
| 14 | type JustNumbers = Extract<NumbersAndStrings, number>; |
| 15 | // number |
| 16 | |
| 17 | // Extract with object types — keep matching shapes |
| 18 | type Shape = |
| 19 | | { kind: "circle"; radius: number } |
| 20 | | { kind: "square"; side: number } |
| 21 | | { kind: "triangle"; base: number; height: number }; |
| 22 | |
| 23 | type Circles = Extract<Shape, { kind: "circle" }>; |
| 24 | // { kind: "circle"; radius: number } |
| 25 | |
| 26 | // Exclude with discriminated unions |
| 27 | type NonCircles = Exclude<Shape, { kind: "circle" }>; |
| 28 | // { kind: "square"; side: number } | { kind: "triangle"; base: number; height: number } |
| 29 | |
| 30 | // Practical: filtering function overloads |
| 31 | type EventType = "click" | "hover" | "focus" | "blur" | "scroll"; |
| 32 | type UserEvents = Exclude<EventType, "scroll" | "resize">; |
| 33 | // "click" | "hover" | "focus" | "blur" |
info
NonNullable<T> removes null and undefined from a type. It is the simplest utility and one of the most frequently needed in real-world code.
| 1 | type Maybe = string | null | undefined; |
| 2 | type Definitely = NonNullable<Maybe>; |
| 3 | // string |
| 4 | |
| 5 | // Works with object types too |
| 6 | type NullableConfig = { host: string } | null; |
| 7 | type Config = NonNullable<NullableConfig>; |
| 8 | // { host: string } |
| 9 | |
| 10 | // Practical: filtering arrays |
| 11 | const values: (string | null)[] = ["a", null, "b", undefined, "c"]; |
| 12 | const filtered: NonNullable<typeof values[number]>[] = values.filter( |
| 13 | (v): v is NonNullable<typeof v> => v != null |
| 14 | ); |
| 15 | // string[] — null and undefined removed |
| 16 | |
| 17 | // Combined with Array.filter type guard |
| 18 | function nonNull<T>(items: T[]): NonNullable<T>[] { |
| 19 | return items.filter( |
| 20 | (item): item is NonNullable<T> => item != null |
| 21 | ); |
| 22 | } |
| 23 | |
| 24 | const result = nonNull([1, null, 2, undefined, 3]); |
| 25 | // number[] — inferred correctly |
| 26 | |
| 27 | // NonNullable also removes only null/undefined, not false or 0 |
| 28 | type Test = NonNullable<string | number | boolean | null | undefined | 0 | false>; |
| 29 | // string | number | boolean | 0 | false |
info
These utility types extract type information from function signatures. ReturnType<T> gets the return type, and Parameters<T> gets a tuple of parameter types.
| 1 | function fetchUser(id: number, opts?: { cache: boolean }): Promise<User> { |
| 2 | return fetch(`/api/users/${id}`).then((r) => r.json()); |
| 3 | } |
| 4 | |
| 5 | // Extract return type |
| 6 | type FetchUserReturn = ReturnType<typeof fetchUser>; |
| 7 | // Promise<User> |
| 8 | |
| 9 | // Extract parameter types as a tuple |
| 10 | type FetchUserParams = Parameters<typeof fetchUser>; |
| 11 | // [id: number, opts?: { cache: boolean } | undefined] |
| 12 | |
| 13 | // Access individual parameter by index |
| 14 | type FirstParam = Parameters<typeof fetchUser>[0]; |
| 15 | // number |
| 16 | |
| 17 | type SecondParam = Parameters<typeof fetchUser>[1]; |
| 18 | // { cache: boolean } | undefined |
| 19 | |
| 20 | // Practical: wrapping functions with same signature |
| 21 | function withLogging<T extends (...args: any[]) => any>( |
| 22 | fn: T |
| 23 | ): (...args: Parameters<T>) => ReturnType<T> { |
| 24 | return (...args: Parameters<T>): ReturnType<T> => { |
| 25 | console.log("Calling:", fn.name); |
| 26 | return fn(...args); |
| 27 | }; |
| 28 | } |
| 29 | |
| 30 | const wrappedFetch = withLogging(fetchUser); |
| 31 | |
| 32 | // Practical: extracting from generic functions |
| 33 | function createResponse<T>(data: T, status: number = 200): { data: T; status: number } { |
| 34 | return { data, status }; |
| 35 | } |
| 36 | |
| 37 | type CreateResponseParams = Parameters<typeof createResponse>; |
| 38 | // [data: unknown, status?: number] |
| 39 | // Note: generic type parameters resolve to their constraints |
| 40 | |
| 41 | // Practical: mock factory from function signature |
| 42 | function createMock<T extends (...args: any[]) => any>( |
| 43 | fn: T, |
| 44 | impl?: (...args: Parameters<T>) => ReturnType<T> |
| 45 | ): (...args: Parameters<T>) => ReturnType<T> { |
| 46 | return impl ?? (() => ({} as ReturnType<T>)); |
| 47 | } |
best practice
ConstructorParameters<T> extracts parameter types from a constructor function. InstanceType<T> extracts the instance type from a constructor. These are the class-equivalents of Parameters and ReturnType.
| 1 | class Database { |
| 2 | constructor( |
| 3 | public host: string, |
| 4 | public port: number, |
| 5 | private password: string |
| 6 | ) {} |
| 7 | connect(): Promise<void> { |
| 8 | return Promise.resolve(); |
| 9 | } |
| 10 | } |
| 11 | |
| 12 | // Constructor parameters as tuple |
| 13 | type DBConstructorParams = ConstructorParameters<typeof Database>; |
| 14 | // [host: string, port: number, password: string] |
| 15 | |
| 16 | // Instance type |
| 17 | type DBInstance = InstanceType<typeof Database>; |
| 18 | // Database — the full instance type |
| 19 | |
| 20 | // Practical: factory function |
| 21 | function createInstance<T extends new (...args: any[]) => any>( |
| 22 | ctor: T, |
| 23 | ...args: ConstructorParameters<T> |
| 24 | ): InstanceType<T> { |
| 25 | return new ctor(...args); |
| 26 | } |
| 27 | |
| 28 | const db = createInstance(Database, "localhost", 5432, "secret"); |
| 29 | // typeof db — Database |
| 30 | |
| 31 | // Practical: dependency injection container |
| 32 | class Container { |
| 33 | private instances = new Map<string, any>(); |
| 34 | |
| 35 | register<T extends new (...args: any[]) => any>( |
| 36 | token: string, |
| 37 | ctor: T, |
| 38 | ...deps: ConstructorParameters<T> |
| 39 | ): InstanceType<T> { |
| 40 | const instance = new ctor(...deps); |
| 41 | this.instances.set(token, instance); |
| 42 | return instance; |
| 43 | } |
| 44 | |
| 45 | resolve<T>(token: string): T | undefined { |
| 46 | return this.instances.get(token) as T | undefined; |
| 47 | } |
| 48 | } |
Awaited<T> (added in TypeScript 4.5) recursively unwraps Promise<T> types, including nested promises. It models the behavior of await at the type level.
| 1 | // Basic — unwrap single promise |
| 2 | type PromiseString = Promise<string>; |
| 3 | type Resolved = Awaited<PromiseString>; |
| 4 | // string |
| 5 | |
| 6 | // Nested promises — recursive unwrap |
| 7 | type DeepPromise = Promise<Promise<Promise<number>>>; |
| 8 | type DeepResolved = Awaited<DeepPromise>; |
| 9 | // number |
| 10 | |
| 11 | // Mixed with union types |
| 12 | type Mixed = Promise<string | number>; |
| 13 | type MixedResolved = Awaited<Mixed>; |
| 14 | // string | number |
| 15 | |
| 16 | // Practical: typing async function results |
| 17 | async function fetchData(): Promise<{ id: number; name: string }> { |
| 18 | return { id: 1, name: "Alice" }; |
| 19 | } |
| 20 | |
| 21 | type FetchResult = Awaited<ReturnType<typeof fetchData>>; |
| 22 | // { id: number; name: string } |
| 23 | |
| 24 | // Practical: array of promises |
| 25 | async function allSettled<T extends readonly any[]>( |
| 26 | promises: T |
| 27 | ): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }> { |
| 28 | return Promise.all(promises) as any; |
| 29 | } |
| 30 | |
| 31 | const results = await allSettled([ |
| 32 | Promise.resolve(42), |
| 33 | Promise.resolve("hello"), |
| 34 | ]); |
| 35 | // results: [number, string] |
| 36 | |
| 37 | // Practical: type-safe Promise.all wrapper |
| 38 | function safeAll<T extends readonly any[]>( |
| 39 | promises: T |
| 40 | ): Promise<{ [P in keyof T]: Awaited<T[P]> }> { |
| 41 | return Promise.all(promises) as any; |
| 42 | } |
| 43 | |
| 44 | // Before Awaited, people used nested conditional types: |
| 45 | type Unpromise<T> = T extends Promise<infer U> ? Unpromise<U> : T; |
| 46 | // Awaited is the built-in version of this pattern |
info
ThisParameterType<T> extracts the this parameter type from a function. OmitThisParameter<T> removes the this parameter, giving you just the regular function signature.
| 1 | function onClick(this: HTMLElement, event: MouseEvent): void { |
| 2 | console.log("Clicked:", this.tagName); |
| 3 | } |
| 4 | |
| 5 | // Extract the 'this' type |
| 6 | type ThisType = ThisParameterType<typeof onClick>; |
| 7 | // HTMLElement |
| 8 | |
| 9 | // Remove the 'this' parameter |
| 10 | type WithoutThis = OmitThisParameter<typeof onClick>; |
| 11 | // (event: MouseEvent) => void |
| 12 | |
| 13 | // Practical: binding methods |
| 14 | function bind<T extends (this: any, ...args: any[]) => any>( |
| 15 | fn: T, |
| 16 | thisArg: ThisParameterType<T> |
| 17 | ): OmitThisParameter<T> { |
| 18 | return fn.bind(thisArg) as OmitThisParameter<T>; |
| 19 | } |
| 20 | |
| 21 | class Timer { |
| 22 | private seconds = 0; |
| 23 | |
| 24 | tick(this: Timer): void { |
| 25 | this.seconds++; |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | const timer = new Timer(); |
| 30 | const boundTick = bind(timer.tick, timer); |
| 31 | // boundTick has type () => void — no 'this' parameter |
| 32 | |
| 33 | // Practical: safe event handler binding |
| 34 | function createHandler<T extends HTMLElement>( |
| 35 | element: T, |
| 36 | handler: (this: T, event: Event) => void |
| 37 | ): OmitThisParameter<typeof handler> { |
| 38 | return handler.bind(element) as any; |
| 39 | } |
TypeScript 4.1 introduced template literal types along with four intrinsic string manipulation utilities: Uppercase, Lowercase, Capitalize, and Uncapitalize.
| 1 | // Uppercase — converts string literal to uppercase |
| 2 | type Upper = Uppercase<"hello">; |
| 3 | // "HELLO" |
| 4 | |
| 5 | // Lowercase — converts string literal to lowercase |
| 6 | type Lower = Lowercase<"HELLO">; |
| 7 | // "hello" |
| 8 | |
| 9 | // Capitalize — capitalizes first character |
| 10 | type Cap = Capitalize<"hello world">; |
| 11 | // "Hello world" |
| 12 | |
| 13 | // Uncapitalize — lowercases first character |
| 14 | type Uncap = Uncapitalize<"Hello">; |
| 15 | // "hello" |
| 16 | |
| 17 | // Practical: generating getters/setters from field names |
| 18 | type Getters<T> = { |
| 19 | [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; |
| 20 | }; |
| 21 | |
| 22 | type Setters<T> = { |
| 23 | [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void; |
| 24 | }; |
| 25 | |
| 26 | type Person = { name: string; age: number }; |
| 27 | type PersonAccessors = Getters<Person> & Setters<Person>; |
| 28 | // { getName: () => string; getAge: () => number; |
| 29 | // setName: (value: string) => void; setAge: (value: number) => void } |
| 30 | |
| 31 | // Practical: CSS property mapping |
| 32 | type CSSProperty = "background-color" | "font-size" | "margin-top"; |
| 33 | |
| 34 | type CamelCase<T extends string> = |
| 35 | T extends `${infer P}-${infer Rest}` |
| 36 | ? `${P}${Capitalize<CamelCase<Rest>>}` |
| 37 | : T; |
| 38 | |
| 39 | type CamelProperties = { |
| 40 | [K in CSSProperty as CamelCase<K>]: string; |
| 41 | }; |
| 42 | // { backgroundColor: string; fontSize: string; marginTop: string } |
| 43 | |
| 44 | // Practical: event handler naming |
| 45 | type EventName = "click" | "focus" | "blur" | "submit"; |
| 46 | type EventHandlers = { |
| 47 | [K in EventName as `on${Capitalize<K>}`]: (event: any) => void; |
| 48 | }; |
| 49 | // { onClick: (event: any) => void; onFocus: (event: any) => void; ... } |
info
1. Prefer Pick over Omit when you control the source type — Pick automatically updates when new properties are added.
2. Use Partial<T> for function parameters that represent updates, not full entities.
3. Combine ReturnType and Parameters to avoid duplicating function signatures in wrappers and higher-order functions.
4. Use Record<K, V> instead of index signatures with string keys for constrained dictionaries.
5. Leverage Awaited<T> instead of manual promise unwrapping conditional types.
6. String manipulation utilities are powerful but only work with literal types — use them in mapped type key remapping for maximum effect.
7. NonNullable<T> is type-safe — prefer it over the ! non-null assertion operator.
8. Remember that utility types are shallow — Readonly<T> and Partial<T> only affect the top level. Combine with recursive types for deep transformations.