|$ curl https://forge-ai.dev/api/markdown?path=docs/typescript/utility-types
$cat docs/typescript-—-utility-types.md
updated Recently·14 min read·published

TypeScript — Utility Types

TypeScriptIntermediate
Introduction

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

All utility types ship with TypeScript globally — no import needed. They are defined in lib.es5.d.ts and expanded in lib.es2015.d.ts through the standard library declaration files.
Partial<T> & Required<T>

Partial<T> makes every property optional. Required<T> makes every property required, removing optional modifiers. These are inverses: Required<Partial<T>> restores the original.

partial-required.ts
TypeScript
1interface User {
2 id: number;
3 name: string;
4 email: string;
5 role: "admin" | "user";
6}
7
8// Partial — all properties become optional
9type PartialUser = Partial<User>;
10// { id?: number; name?: string; email?: string; role?: "admin" | "user" }
11
12function updateUser(id: number, updates: Partial<User>): void {
13 // Only pass what changed
14}
15
16updateUser(1, { name: "Alice" }); // ok
17updateUser(1, {}); // ok — empty partial
18
19// Required — all properties become mandatory (removes ?)
20type RequiredUser = Required<PartialUser>;
21// { id: number; name: string; email: string; role: "admin" | "user" }
22
23// Practical: update API with partial payload
24function 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

Partial<T> is the most-used utility type. Use it for update operations, form state, and any scenario where you only need a subset of properties. Required<T> is useful when validating that all fields are present after a partial input phase.
Readonly<T>

Readonly<T> adds the readonly modifier to every property, making the type deeply immutability-safe at the type level (shallow only).

readonly.ts
TypeScript
1interface Config {
2 host: string;
3 port: number;
4 timeout: number;
5}
6
7type ImmutableConfig = Readonly<Config>;
8// { readonly host: string; readonly port: number; readonly timeout: number }
9
10const 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
19interface AppConfig {
20 database: { host: string; port: number };
21}
22
23type 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)
27type 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
32function freezeConfig<T extends object>(cfg: T): Readonly<T> {
33 return Object.freeze(cfg) as Readonly<T>;
34}

best practice

Readonly<T> works best for configuration objects, redux-like state, and immutable data flows. For true immutability at runtime, pair it with Object.freeze() or a library like Immer.
Pick<T, K> & Omit<T, K>

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.

pick-omit.ts
TypeScript
1interface 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
12type ProductPreview = Pick<Product, "id" | "name" | "price">;
13// { id: number; name: string; price: number }
14
15// Omit — remove specific keys
16type ProductInput = Omit<Product, "id" | "createdAt" | "updatedAt">;
17// { name: string; price: number; description: string; category: string }
18
19// Pick with computed keys
20type Keys = "a" | "b";
21type 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
28interface APIResponse<T> {
29 data: T;
30 status: number;
31 message: string;
32 timestamp: string;
33}
34
35// Client only cares about data and status
36type ClientResponse<T> = Pick<APIResponse<T>, "data" | "status">;
37
38// Create without timestamps
39type CreateProduct = Omit<Product, "createdAt" | "updatedAt"> & {
40 tags: string[];
41};
pick-omit-union.ts
TypeScript
1// Pick and Omit work with union types too
2type 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
8type ClickEvent = Extract<Event, { type: "click" }>;
9// { type: "click"; x: number; y: number }
10
11// Omit can remove discriminated branches
12type NonKeyboardEvents = Omit<Event, { type: "keypress" }>;
13// This does NOT work as expected with unions!
14
15// Correct approach — use Exclude with Extract:
16type NonKeyboardEvents2 = Exclude<Event, { type: "keypress" }>;
17// { type: "click"; ... } | { type: "focus"; ... }

info

Pick is the safer of the two — if the underlying interface changes, Pick automatically picks up new keys while Omit silently includes them. Prefer Pick when you know exactly which keys you want.
Record<K, V>

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.

record.ts
TypeScript
1// Basic — map string keys to number values
2type PageViews = Record<string, number>;
3const views: PageViews = { "/": 100, "/about": 50, "/contact": 25 };
4
5// Constrain keys to a union
6type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
7type Handler = () => void;
8
9type RouteHandlers = Record<HttpMethod, Handler>;
10const 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
18type UserRole = "admin" | "editor" | "viewer";
19type Permissions = Record<UserRole, string[]>;
20
21const rolePermissions: Permissions = {
22 admin: ["create", "read", "update", "delete"],
23 editor: ["create", "read", "update"],
24 viewer: ["read"],
25};
26
27// Nested records
28type Config = Record<string, Record<string, string | number>>;
29const appConfig: Config = {
30 database: { host: "localhost", port: 5432 },
31 cache: { host: "localhost", port: 6379 },
32};
33
34// Record with optional-like behavior via Partial
35type PartialPermissions = Partial<Record<UserRole, string[]>>;
36// { admin?: string[]; editor?: string[]; viewer?: string[] }
37
38// Record for enum-like mapping
39enum Color {
40 Red = "RED",
41 Green = "GREEN",
42 Blue = "BLUE",
43}
44
45type ColorMap = Record<Color, string>;
46const hexColors: ColorMap = {
47 [Color.Red]: "#ff0000",
48 [Color.Green]: "#00ff00",
49 [Color.Blue]: "#0000ff",
50};

best practice

Record is ideal for creating typed dictionaries and lookup tables. Use it instead of index signatures ({[key: string]: V}) when you need to constrain keys or ensure all members of a union are present.
Exclude<T, U> & Extract<T, U>

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.

exclude-extract.ts
TypeScript
1// Exclude — remove types from a union
2type AllTypes = string | number | boolean | null | undefined;
3type PrimitiveOnly = Exclude<AllTypes, null | undefined>;
4// string | number | boolean
5
6type WithoutString = Exclude<string | number | boolean, string>;
7// number | boolean
8
9// Extract — keep only matching types
10type NumbersAndStrings = string | number | boolean | null;
11type JustStrings = Extract<NumbersAndStrings, string>;
12// string
13
14type JustNumbers = Extract<NumbersAndStrings, number>;
15// number
16
17// Extract with object types — keep matching shapes
18type Shape =
19 | { kind: "circle"; radius: number }
20 | { kind: "square"; side: number }
21 | { kind: "triangle"; base: number; height: number };
22
23type Circles = Extract<Shape, { kind: "circle" }>;
24// { kind: "circle"; radius: number }
25
26// Exclude with discriminated unions
27type NonCircles = Exclude<Shape, { kind: "circle" }>;
28// { kind: "square"; side: number } | { kind: "triangle"; base: number; height: number }
29
30// Practical: filtering function overloads
31type EventType = "click" | "hover" | "focus" | "blur" | "scroll";
32type UserEvents = Exclude<EventType, "scroll" | "resize">;
33// "click" | "hover" | "focus" | "blur"

info

Exclude and Extract are the foundation of type-level filtering. Omit<T, K> is internally implemented as Pick<T, Exclude<keyof T, K>>.
NonNullable<T>

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.

nonnullable.ts
TypeScript
1type Maybe = string | null | undefined;
2type Definitely = NonNullable<Maybe>;
3// string
4
5// Works with object types too
6type NullableConfig = { host: string } | null;
7type Config = NonNullable<NullableConfig>;
8// { host: string }
9
10// Practical: filtering arrays
11const values: (string | null)[] = ["a", null, "b", undefined, "c"];
12const 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
18function nonNull<T>(items: T[]): NonNullable<T>[] {
19 return items.filter(
20 (item): item is NonNullable<T> => item != null
21 );
22}
23
24const result = nonNull([1, null, 2, undefined, 3]);
25// number[] — inferred correctly
26
27// NonNullable also removes only null/undefined, not false or 0
28type Test = NonNullable<string | number | boolean | null | undefined | 0 | false>;
29// string | number | boolean | 0 | false

info

NonNullable is the type-safe alternative to the ! (non-null assertion). Instead of x!.name, derive NonNullable<typeof x> for flow-typed narrowing.
ReturnType<T> & Parameters<T>

These utility types extract type information from function signatures. ReturnType<T> gets the return type, and Parameters<T> gets a tuple of parameter types.

returntype-parameters.ts
TypeScript
1function fetchUser(id: number, opts?: { cache: boolean }): Promise<User> {
2 return fetch(`/api/users/${id}`).then((r) => r.json());
3}
4
5// Extract return type
6type FetchUserReturn = ReturnType<typeof fetchUser>;
7// Promise<User>
8
9// Extract parameter types as a tuple
10type FetchUserParams = Parameters<typeof fetchUser>;
11// [id: number, opts?: { cache: boolean } | undefined]
12
13// Access individual parameter by index
14type FirstParam = Parameters<typeof fetchUser>[0];
15// number
16
17type SecondParam = Parameters<typeof fetchUser>[1];
18// { cache: boolean } | undefined
19
20// Practical: wrapping functions with same signature
21function 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
30const wrappedFetch = withLogging(fetchUser);
31
32// Practical: extracting from generic functions
33function createResponse<T>(data: T, status: number = 200): { data: T; status: number } {
34 return { data, status };
35}
36
37type 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
42function 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

Use ReturnType and Parameters to derive types from existing functions rather than duplicating them. This is essential for higher-order functions, decorators, and wrapper patterns.
ConstructorParameters & InstanceType

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.

constructor-instance.ts
TypeScript
1class 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
13type DBConstructorParams = ConstructorParameters<typeof Database>;
14// [host: string, port: number, password: string]
15
16// Instance type
17type DBInstance = InstanceType<typeof Database>;
18// Database — the full instance type
19
20// Practical: factory function
21function createInstance<T extends new (...args: any[]) => any>(
22 ctor: T,
23 ...args: ConstructorParameters<T>
24): InstanceType<T> {
25 return new ctor(...args);
26}
27
28const db = createInstance(Database, "localhost", 5432, "secret");
29// typeof db — Database
30
31// Practical: dependency injection container
32class 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>

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.

awaited.ts
TypeScript
1// Basic — unwrap single promise
2type PromiseString = Promise<string>;
3type Resolved = Awaited<PromiseString>;
4// string
5
6// Nested promises — recursive unwrap
7type DeepPromise = Promise<Promise<Promise<number>>>;
8type DeepResolved = Awaited<DeepPromise>;
9// number
10
11// Mixed with union types
12type Mixed = Promise<string | number>;
13type MixedResolved = Awaited<Mixed>;
14// string | number
15
16// Practical: typing async function results
17async function fetchData(): Promise<{ id: number; name: string }> {
18 return { id: 1, name: "Alice" };
19}
20
21type FetchResult = Awaited<ReturnType<typeof fetchData>>;
22// { id: number; name: string }
23
24// Practical: array of promises
25async 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
31const results = await allSettled([
32 Promise.resolve(42),
33 Promise.resolve("hello"),
34]);
35// results: [number, string]
36
37// Practical: type-safe Promise.all wrapper
38function 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:
45type Unpromise<T> = T extends Promise<infer U> ? Unpromise<U> : T;
46// Awaited is the built-in version of this pattern

info

Before TypeScript 4.5, you would write recursive conditional types to unwrap promises. Awaited<T> is the canonical built-in replacement. Always prefer it over custom implementations.
ThisParameterType & OmitThisParameter

ThisParameterType<T> extracts the this parameter type from a function. OmitThisParameter<T> removes the this parameter, giving you just the regular function signature.

this-parameter.ts
TypeScript
1function onClick(this: HTMLElement, event: MouseEvent): void {
2 console.log("Clicked:", this.tagName);
3}
4
5// Extract the 'this' type
6type ThisType = ThisParameterType<typeof onClick>;
7// HTMLElement
8
9// Remove the 'this' parameter
10type WithoutThis = OmitThisParameter<typeof onClick>;
11// (event: MouseEvent) => void
12
13// Practical: binding methods
14function 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
21class Timer {
22 private seconds = 0;
23
24 tick(this: Timer): void {
25 this.seconds++;
26 }
27}
28
29const timer = new Timer();
30const boundTick = bind(timer.tick, timer);
31// boundTick has type () => void — no 'this' parameter
32
33// Practical: safe event handler binding
34function 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}
String Manipulation Utilities

TypeScript 4.1 introduced template literal types along with four intrinsic string manipulation utilities: Uppercase, Lowercase, Capitalize, and Uncapitalize.

string-manipulation.ts
TypeScript
1// Uppercase — converts string literal to uppercase
2type Upper = Uppercase<"hello">;
3// "HELLO"
4
5// Lowercase — converts string literal to lowercase
6type Lower = Lowercase<"HELLO">;
7// "hello"
8
9// Capitalize — capitalizes first character
10type Cap = Capitalize<"hello world">;
11// "Hello world"
12
13// Uncapitalize — lowercases first character
14type Uncap = Uncapitalize<"Hello">;
15// "hello"
16
17// Practical: generating getters/setters from field names
18type Getters<T> = {
19 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
20};
21
22type Setters<T> = {
23 [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
24};
25
26type Person = { name: string; age: number };
27type 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
32type CSSProperty = "background-color" | "font-size" | "margin-top";
33
34type CamelCase<T extends string> =
35 T extends `${infer P}-${infer Rest}`
36 ? `${P}${Capitalize<CamelCase<Rest>>}`
37 : T;
38
39type CamelProperties = {
40 [K in CSSProperty as CamelCase<K>]: string;
41};
42// { backgroundColor: string; fontSize: string; marginTop: string }
43
44// Practical: event handler naming
45type EventName = "click" | "focus" | "blur" | "submit";
46type EventHandlers = {
47 [K in EventName as `on${Capitalize<K>}`]: (event: any) => void;
48};
49// { onClick: (event: any) => void; onFocus: (event: any) => void; ... }

info

String manipulation utilities only work with string literal types, not string itself. They are most powerful when combined with template literal types and mapped type key remapping.
Best Practices

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.

$Blueprint — Engineering Documentation·Section ID: TS-UTIL·Revision: 1.0