TypeScript utility types are generic types that ship with the language and transform one type into another. Instead of writing a second UserUpdate interface by hand, you write Partial<User>. Instead of copying a function's return type into a new alias, you write ReturnType<typeof fn>. When the original type changes, every derived type follows automatically, so the types can't drift apart.
This guide covers the utility types you will use most often, grouped by what they do, and shows how each one is implemented. They are all built from mapped types and conditional types, so once you can read their definitions you can write your own.
Quick reference: TypeScript utility types
| Utility type | What it does | Example result |
|---|---|---|
Partial<T> | makes every property optional | { name?: string } |
Required<T> | makes every property required | { name: string } |
Readonly<T> | makes every property readonly | { readonly name: string } |
Record<K, V> | object type with keys K and values V | { a: number; b: number } |
Pick<T, K> | keeps only keys K | { id: number } |
Omit<T, K> | removes keys K | { name: string } |
Exclude<U, E> | removes union members assignable to E | "a" | "b" |
Extract<U, E> | keeps union members assignable to E | "c" |
NonNullable<T> | removes null and undefined | string |
ReturnType<F> | a function's return type | Promise<User> |
Parameters<F> | a function's parameters as a tuple | [id: number] |
Uppercase<S> and friends | transform string literal types | "GET" |
The examples below share one model:
interface User {
id: number;
name: string;
email: string;
role: "admin" | "member" | "guest";
deletedAt: Date | null;
}Partial, Required and Readonly: changing property modifiers
Partial for updates and patches
Partial<T> makes every property optional. It fits PATCH endpoints and update functions where the caller sends only the fields that change:
function updateUser(id: number, changes: Partial<User>): Promise<User> {
return db.users.update(id, changes);
}
updateUser(1, { name: "Somsri" }); // OK
// updateUser(1, { nickname: "S" }); // Error: 'nickname' does not exist in type 'Partial<User>'Its definition is a one-line mapped type:
type Partial<T> = { [P in keyof T]?: T[P] };Partial is shallow. Nested objects keep their original required properties.
Required and the -? modifier
Required<T> does the opposite and removes every ?. It is useful for configuration: accept a partial config from the caller, merge in defaults, and return a complete one.
type Options = { retries?: number; timeoutMs?: number };
const defaults: Required<Options> = { retries: 3, timeoutMs: 5000 };
function withDefaults(opts: Options): Required<Options> {
return { ...defaults, ...opts };
}The interesting part is how it is written. A mapped type can add a modifier with ? or +?, and remove one with -?:
type Person = { name?: string; age?: number; address?: string };
type MyRequired<T> = { [K in keyof T]-?: T[K] };
type MyRequiredPerson = MyRequired<Person>;
// { name: string; age: number; address: string }Removing ? also removes the undefined that optionality added, so name becomes string, not string | undefined.
One caution about withDefaults: spreading an object that contains { retries: undefined } overwrites the default with undefined, and the compiler won't catch it unless you enable exactOptionalPropertyTypes, which forbids assigning undefined to an optional property that doesn't list it explicitly.
Readonly and -readonly
Readonly<T> adds readonly to every property, so the compiler rejects reassignment:
const admin: Readonly<User> = await getUser(1);
// admin.role = "guest"; // Error: Cannot assign to 'role' because it is a read-only property.It only exists at compile time and is shallow. Use Object.freeze if you need runtime protection. The reverse, a Mutable type, uses the -readonly modifier:
type Mutable<T> = { -readonly [K in keyof T]: T[K] };Record: typed dictionaries and lookup tables
Record<K, V> builds an object type whose keys are K and whose values are all V. You could write it yourself:
type MyRecord<T extends string | number | symbol, U> = { [K in T]: U };
type MyPerson = MyRecord<"name" | "address", string>;
// same as { name: string; address: string }
type MyPerson2 = Record<"name" | "address", string>;Its best use is a lookup table keyed by a union. If someone adds a new role later, the compiler flags every table that is missing it:
const permissions: Record<User["role"], string[]> = {
admin: ["read", "write", "delete"],
member: ["read", "write"],
guest: ["read"],
};Record<string, V> works as a dictionary type, but TypeScript assumes every key exists. cache["missing"] is typed V, not V | undefined, unless you turn on noUncheckedIndexedAccess. For dynamic keys, a Map<string, V> is often clearer.
Pick and Omit: selecting properties
Pick<T, K> keeps the listed keys. Omit<T, K> removes them:
type UserSummary = Pick<User, "id" | "name">;
// { id: number; name: string }
type NewUser = Omit<User, "id" | "deletedAt">;
// { name: string; email: string; role: "admin" | "member" | "guest" }A typical pattern is a create payload that omits server-generated fields and a public view that picks only safe fields.
Two things to know about Omit:
-
It doesn't check key names.
PickrequiresK extends keyof T, butOmitaccepts any string, soOmit<User, "emial">compiles and silently omits nothing. If that worries you, define a strict version:tstype StrictOmit<T, K extends keyof T> = Omit<T, K>; -
It doesn't distribute over unions.
Omit<A | B, "id">only keeps the properties common toAandB. For discriminated unions, use a distributive version:tstype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
Exclude, Extract and NonNullable: filtering unions
These three work on union types rather than object types:
type Role = User["role"]; // "admin" | "member" | "guest"
type StaffRole = Exclude<Role, "guest">; // "admin" | "member"
type GuestRole = Extract<Role, "guest" | "bot">; // "guest"
type Deleted = NonNullable<User["deletedAt"]>; // DateTheir definitions are distributive conditional types:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type NonNullable<T> = T & {};Because the condition runs once per union member, Exclude drops the matching members and Extract keeps them. NonNullable has been defined as T & {} since TypeScript 4.8: intersecting with {} removes null and undefined and leaves everything else. TypeScript generics and conditional types explains distribution in detail.
Extract also works on discriminated unions:
type UiEvent =
| { type: "click"; x: number; y: number }
| { type: "keypress"; key: string };
type ClickEvent = Extract<UiEvent, { type: "click" }>;ReturnType and Parameters: types from functions
Sometimes the function is the source of truth and you want types that follow it, especially for functions from a library that doesn't export its types.
async function fetchUser(id: number, opts?: { signal?: AbortSignal }) {
const res = await fetch(`/api/users/${id}`, opts);
return (await res.json()) as User;
}
type FetchUserResult = ReturnType<typeof fetchUser>; // Promise<User>
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>; // User
type FetchUserArgs = Parameters<typeof fetchUser>; // [id: number, opts?: { signal?: AbortSignal }]
type FetchUserOptions = Parameters<typeof fetchUser>[1]; // { signal?: AbortSignal } | undefinedNote the typeof. ReturnType takes a function type, not a function value. For async functions, wrap the result in Awaited to get the resolved value.
Both are built with infer:
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;Parameters is handy for wrappers that forward arguments unchanged:
function withLogging<F extends (...args: any[]) => any>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
console.log(`calling ${fn.name}`, args);
return fn(...args);
};
}Intrinsic string types: Uppercase, Lowercase, Capitalize, Uncapitalize
Four utility types transform string literal types. They are called intrinsic because the compiler implements them directly rather than through a type definition:
type Method = "get" | "post";
type A = Uppercase<Method>; // "GET" | "POST"
type B = Lowercase<"X-Request-ID">; // "x-request-id"
type C = Capitalize<"click">; // "Click"
type D = Uncapitalize<"UserId">; // "userId"They are most useful with template literal types and key remapping in mapped types. This builds event handler names from an event map:
type Events = { click: MouseEvent; focus: FocusEvent };
type Handlers = {
[K in keyof Events as `on${Capitalize<K & string>}`]: (e: Events[K]) => void;
};
// { onClick: (e: MouseEvent) => void; onFocus: (e: FocusEvent) => void }The as clause renames each key. TypeScript advanced types covers mapped types and key remapping further.
Writing your own utility types
When the built-ins don't fit, combine them. Two helpers that come up in real projects:
// Make only some keys optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type UserInput = PartialBy<User, "role" | "deletedAt">;
// Recursively optional, for nested config objects
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};Hovering over UserInput in the editor shows an intersection that is hard to read. A common trick is a Simplify helper that flattens it into one object type:
type Simplify<T> = { [K in keyof T]: T[K] } & {};
type UserInputFlat = Simplify<PartialBy<User, "role" | "deletedAt">>;Keep custom helpers in one file, give them clear names, and add a short comment with an example result. DeepPartial as written also recurses into arrays and functions, which is usually fine for config objects but worth knowing.
FAQ
What is the difference between Pick and Omit?
Pick<T, K> keeps only the listed keys, and Omit<T, K> keeps everything except them. Use Pick when you want a few fields and Omit when you want most of them. Pick checks that the keys exist, but Omit doesn't.
Is Partial deep in TypeScript?
No. Partial only makes top-level properties optional. For nested objects, write a recursive DeepPartial type like the one above.
How do I get the return type of an async function?
Use Awaited<ReturnType<typeof fn>>. ReturnType alone gives you Promise<T>, and Awaited unwraps it to T.
When should I use Record instead of an index signature?
Record<"a" | "b", V> with a union of keys requires every key to be present, which makes it good for exhaustive lookup tables. For arbitrary string keys, Record<string, V> and { [key: string]: V } are equivalent, so pick whichever your team reads more easily.
Are utility types checked at runtime?
No. Like all TypeScript types they are erased during compilation. Readonly won't stop a mutation at runtime, and Partial won't validate a request body. Pair them with runtime validation at system boundaries.
A short checklist
- Derive types instead of duplicating them:
Partialfor updates,Omitfor create payloads,Pickfor public views. - Use
Recordkeyed by a union for lookup tables so new members can't be forgotten. - Filter unions with
Exclude,ExtractandNonNullable. - Take types from functions with
ReturnType,ParametersandAwaited. - Remember that
PartialandReadonlyare shallow, and thatOmitdoesn't check keys. - Write custom helpers with mapped types and the
-?and-readonlymodifiers.
Derived types keep a large codebase consistent as it changes. If you're planning a TypeScript project and want help with its type design and architecture, Vectorkub builds web applications and can help.
