TypeScript Utility Types: Complete Guide with Examples

Published February 12, 2026 · 15 min read

TypeScript ships with over 20 built-in utility types that transform existing types into new ones. Instead of writing repetitive type definitions, utility types let you derive new types from your existing ones — making properties optional, picking subsets, creating dictionaries, extracting return types, and much more. This guide covers every built-in utility type with practical examples, then shows you how to build your own.

⚙ Try it: Use our JSON to TypeScript Converter to generate interfaces from JSON data, then apply utility types to transform them.

1. What Are Utility Types?

Utility types are generic types built into TypeScript that take one or more type parameters and produce a transformed type. They solve a fundamental problem: you often need variations of an existing type without duplicating its definition.

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

// Without utility types: manual duplication
interface UserUpdate {
    name?: string;
    email?: string;
    password?: string;
}

// With utility types: derive from the source type
type UserUpdate = Partial<Omit<User, "id" | "createdAt">>;
// { name?: string; email?: string; password?: string; }

When the User interface changes, the derived type updates automatically. This is the core value of utility types: a single source of truth for your type definitions.

2. Object Type Utilities

Partial<T>

Makes all properties of T optional. Ideal for update operations where only some fields may be provided.

interface Config {
    host: string;
    port: number;
    debug: boolean;
}

function updateConfig(current: Config, overrides: Partial<Config>): Config {
    return { ...current, ...overrides };
}

// Only override what you need
updateConfig(defaultConfig, { debug: true });

Required<T>

The opposite of Partial — makes all properties required, removing any ? modifiers.

interface FormFields {
    name?: string;
    email?: string;
    age?: number;
}

// Ensure all fields are filled before submission
function submitForm(data: Required<FormFields>): void {
    // data.name, data.email, data.age are all guaranteed to exist
    console.log(`Submitting: ${data.name}, ${data.email}, ${data.age}`);
}

Readonly<T>

Makes all properties read-only. The compiler prevents reassignment after creation.

interface AppState {
    user: string;
    theme: "light" | "dark";
    notifications: number;
}

function freeze(state: AppState): Readonly<AppState> {
    return Object.freeze(state);
}

const state = freeze({ user: "Alice", theme: "dark", notifications: 3 });
// state.theme = "light";  // Error: Cannot assign to 'theme' (read-only)

Pick<T, K>

Creates a type with only the specified properties from T.

interface Product {
    id: number;
    name: string;
    price: number;
    description: string;
    category: string;
    stock: number;
}

// For a product card, we only need a subset
type ProductCard = Pick<Product, "id" | "name" | "price">;
// { id: number; name: string; price: number; }

function renderCard(product: ProductCard): string {
    return `${product.name}: $${product.price}`;
}

Omit<T, K>

Creates a type with all properties from T except the specified keys. The complement of Pick.

// Remove sensitive fields before sending to the client
type PublicUser = Omit<User, "password">;
// { id: number; name: string; email: string; createdAt: Date; }

// Remove auto-generated fields for creation
type CreateProductInput = Omit<Product, "id">;

function createProduct(input: CreateProductInput): Product {
    return { ...input, id: generateId() };
}

3. Record Type

Record<K, V> creates an object type with keys of type K and values of type V. It is the go-to type for dictionaries and lookup maps.

// String-keyed dictionary
type ErrorMessages = Record<string, string>;
const errors: ErrorMessages = {
    name: "Name is required",
    email: "Invalid email format"
};

// Union keys ensure every member is handled
type Status = "pending" | "active" | "archived";
type StatusConfig = Record<Status, { label: string; color: string }>;

const statusMap: StatusConfig = {
    pending: { label: "Pending", color: "#f59e0b" },
    active: { label: "Active", color: "#10b981" },
    archived: { label: "Archived", color: "#6b7280" }
    // Missing a status? TypeScript reports an error
};

// HTTP status code mapping
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
const handlers: Record<HttpMethod, (url: string) => void> = {
    GET: (url) => fetch(url),
    POST: (url) => fetch(url, { method: "POST" }),
    PUT: (url) => fetch(url, { method: "PUT" }),
    DELETE: (url) => fetch(url, { method: "DELETE" })
};

When you use a union type for K, TypeScript enforces that every member of the union is present as a key, giving you exhaustive coverage.

4. Union Type Utilities

Exclude<T, U>

Removes members from a union type that are assignable to U.

type AllEvents = "click" | "scroll" | "mousemove" | "keydown" | "keyup";

// Remove mouse events
type KeyboardEvents = Exclude<AllEvents, "click" | "scroll" | "mousemove">;
// "keydown" | "keyup"

// Remove null and undefined
type Defined = Exclude<string | number | null | undefined, null | undefined>;
// string | number

Extract<T, U>

Keeps only members from T that are assignable to U. The opposite of Exclude.

type AllTypes = string | number | boolean | object | null;

type Primitives = Extract<AllTypes, string | number | boolean>;
// string | number | boolean

// Extract specific event types from a discriminated union
type AppEvent =
    | { type: "user_login"; userId: string }
    | { type: "user_logout"; userId: string }
    | { type: "page_view"; url: string }
    | { type: "error"; message: string };

type UserEvent = Extract<AppEvent, { type: "user_login" | "user_logout" }>;
// { type: "user_login"; userId: string } | { type: "user_logout"; userId: string }

NonNullable<T>

Removes null and undefined from T. A shorthand for Exclude<T, null | undefined>.

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;  // string

// Useful with optional chaining results
function getEnvVar(key: string): string {
    const value: string | undefined = process.env[key];
    if (!value) throw new Error(`Missing env var: ${key}`);
    return value;  // narrowed to NonNullable<string | undefined> = string
}

5. Function Type Utilities

ReturnType<T>

Extracts the return type of a function type. Invaluable when you need to type a variable based on what a function returns.

function createUser(name: string, role: "admin" | "user") {
    return { id: crypto.randomUUID(), name, role, createdAt: new Date() };
}

// Derive the type from the function instead of duplicating it
type User = ReturnType<typeof createUser>;
// { id: string; name: string; role: "admin" | "user"; createdAt: Date; }

// Works with library functions you don't control
type FetchResult = ReturnType<typeof fetch>;  // Promise<Response>

Parameters<T>

Extracts parameter types as a tuple. Useful for wrapping or extending existing functions.

function log(level: "info" | "warn" | "error", message: string, meta?: object) {
    console.log(`[${level}] ${message}`, meta);
}

type LogParams = Parameters<typeof log>;
// [level: "info" | "warn" | "error", message: string, meta?: object]

// Create a wrapper that adds a timestamp
function timedLog(...args: LogParams): void {
    const [level, message, meta] = args;
    log(level, `${new Date().toISOString()} ${message}`, meta);
}

ConstructorParameters<T>

Extracts constructor parameter types as a tuple, similar to Parameters but for class constructors.

class Database {
    constructor(host: string, port: number, name: string) { /* ... */ }
}

type DbArgs = ConstructorParameters<typeof Database>;
// [host: string, port: number, name: string]

function createDatabase(...args: DbArgs): Database {
    return new Database(...args);
}

6. String Utilities

TypeScript provides four intrinsic string manipulation types that work at the type level.

type Upper = Uppercase<"hello">;        // "HELLO"
type Lower = Lowercase<"HELLO">;        // "hello"
type Cap   = Capitalize<"hello">;       // "Hello"
type Uncap = Uncapitalize<"Hello">;     // "hello"

// Practical: generate event handler names from event types
type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

// Generate getter names from property names
type PropName = "name" | "age" | "email";
type GetterName = `get${Capitalize<PropName>}`;
// "getName" | "getAge" | "getEmail"

7. Promise Utilities

Awaited<T> (TypeScript 4.5+) recursively unwraps Promise types to get the resolved value type.

type A = Awaited<Promise<string>>;              // string
type B = Awaited<Promise<Promise<number>>>;     // number
type C = Awaited<string | Promise<boolean>>;     // string | boolean

// Most useful with async function return types
async function fetchUser(id: string) {
    const res = await fetch(`/api/users/${id}`);
    return res.json() as Promise<{ name: string; email: string }>;
}

// Get the resolved type, not Promise<...>
type UserData = Awaited<ReturnType<typeof fetchUser>>;
// { name: string; email: string }

8. Mapped Types

Mapped types iterate over keys of a type and transform each property. They are the foundation that built-in utility types like Partial, Required, and Readonly are built on.

// This is how Partial is implemented internally:
type MyPartial<T> = {
    [K in keyof T]?: T[K];
};

// And Readonly:
type MyReadonly<T> = {
    readonly [K in keyof T]: T[K];
};

// Make all properties nullable
type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

interface Settings {
    theme: string;
    fontSize: number;
    language: string;
}

type NullableSettings = Nullable<Settings>;
// { theme: string | null; fontSize: number | null; language: string | null; }

Key Remapping with as

TypeScript 4.1 added key remapping, letting you transform property names during mapping.

// Add "get" prefix to all property names
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number; }

// Filter out properties by type (remove non-string properties)
type StringProps<T> = {
    [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type OnlyStrings = StringProps<{ name: string; age: number; email: string }>;
// { name: string; email: string; }

9. Conditional Types

Conditional types choose between two types based on a condition, using the extends keyword as a type-level if statement.

// Basic conditional type
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;    // true
type B = IsString<42>;         // false

// Flatten arrays, leave other types unchanged
type Flatten<T> = T extends Array<infer Item> ? Item : T;

type Str = Flatten<string[]>;     // string
type Num = Flatten<number>;       // number

The infer Keyword

infer declares a type variable within a conditional type, letting you extract types from complex structures.

// Extract the element type from an array
type ElementOf<T> = T extends (infer E)[] ? E : never;
type Item = ElementOf<string[]>;  // string

// Extract the resolved type from a Promise
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;

// Extract the return type of a function (how ReturnType works)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// Extract the first argument type
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type First = FirstArg<(name: string, age: number) => void>;  // string

Distributive Conditional Types

When a conditional type acts on a union, it distributes across each member individually.

type ToArray<T> = T extends any ? T[] : never;

// Distributes over the union:
type Result = ToArray<string | number>;
// string[] | number[]  (NOT (string | number)[])

// Prevent distribution by wrapping in a tuple:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDist<string | number>;
// (string | number)[]

10. Template Literal Types

Template literal types use backtick syntax at the type level to construct string literal types from other types.

// Basic template literal type
type Greeting = `Hello, ${string}`;
const g: Greeting = "Hello, world";   // OK
// const g2: Greeting = "Hi, world";  // Error

// Combine with unions for powerful patterns
type Color = "red" | "blue" | "green";
type Shade = "light" | "dark";
type ColorVariant = `${Shade}-${Color}`;
// "light-red" | "light-blue" | "light-green" | "dark-red" | "dark-blue" | "dark-green"

// CSS property types
type CSSUnit = "px" | "rem" | "em" | "%";
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = "100px";      // OK
const height: CSSValue = "2.5rem";    // OK

// Event listener pattern
type DOMEvent = "click" | "focus" | "blur" | "input";
type EventHandler = `on${Capitalize<DOMEvent>}`;
// "onClick" | "onFocus" | "onBlur" | "onInput"

Pattern Matching with Template Literals

// Extract parts of a string type
type ExtractRoute<S extends string> =
    S extends `/${infer Segment}/${infer Rest}`
        ? Segment | ExtractRoute<`/${Rest}`>
        : S extends `/${infer Segment}`
            ? Segment
            : never;

type Segments = ExtractRoute<"/users/123/posts">;
// "users" | "123" | "posts"

11. Custom Utility Types

Combining mapped types, conditional types, and infer, you can build powerful custom utility types for your projects.

DeepPartial<T>

// Make ALL nested properties optional (Partial only affects top level)
type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface NestedConfig {
    server: { host: string; port: number };
    database: { connection: { host: string; port: number } };
}

type PartialConfig = DeepPartial<NestedConfig>;
// server?: { host?: string; port?: number; }
// database?: { connection?: { host?: string; port?: number; } }

DeepReadonly<T>

type DeepReadonly<T> = {
    readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

Mutable<T> (Remove readonly)

type Mutable<T> = {
    -readonly [K in keyof T]: T[K];
};

interface FrozenUser { readonly name: string; readonly email: string; }
type EditableUser = Mutable<FrozenUser>;
// { name: string; email: string; }

RequiredKeys<T> and OptionalKeys<T>

// Extract keys that are required vs optional
type RequiredKeys<T> = {
    [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

type OptionalKeys<T> = {
    [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

interface Mixed { name: string; age: number; nickname?: string; bio?: string; }
type Req = RequiredKeys<Mixed>;  // "name" | "age"
type Opt = OptionalKeys<Mixed>;  // "nickname" | "bio"

PickByType<T, V>

// Pick only properties whose values match a given type
type PickByType<T, V> = {
    [K in keyof T as T[K] extends V ? K : never]: T[K];
};

interface Mixed { name: string; age: number; active: boolean; email: string; }
type StringFields = PickByType<Mixed, string>;
// { name: string; email: string; }

12. Real-World Patterns

API Response Types

// Generic API response wrapper
type ApiResponse<T> = {
    data: T;
    status: number;
    message: string;
    timestamp: string;
};

type ApiError = {
    error: string;
    code: number;
    details?: Record<string, string[]>;
};

type ApiResult<T> = ApiResponse<T> | ApiError;

// Derive request types from your models
interface Article {
    id: string;
    title: string;
    body: string;
    author: string;
    publishedAt: Date;
    updatedAt: Date;
}

type CreateArticle = Omit<Article, "id" | "publishedAt" | "updatedAt">;
type UpdateArticle = Partial<Pick<Article, "title" | "body">>;
type ArticlePreview = Pick<Article, "id" | "title" | "author" | "publishedAt">;

Form Handling

// Type-safe form state
type FormState<T> = {
    values: T;
    errors: Partial<Record<keyof T, string>>;
    touched: Partial<Record<keyof T, boolean>>;
    isSubmitting: boolean;
};

interface LoginForm { email: string; password: string; remember: boolean; }

type LoginFormState = FormState<LoginForm>;
// {
//   values: { email: string; password: string; remember: boolean; };
//   errors: { email?: string; password?: string; remember?: string; };
//   touched: { email?: boolean; password?: boolean; remember?: boolean; };
//   isSubmitting: boolean;
// }

// Type-safe field change handler
type FieldHandler<T> = <K extends keyof T>(field: K, value: T[K]) => void;

const handleChange: FieldHandler<LoginForm> = (field, value) => {
    // field is narrowed to "email" | "password" | "remember"
    // value matches the field's type
};

State Management

// Type-safe action creators from a state shape
type ActionMap<T> = {
    [K in keyof T]: T[K] extends undefined
        ? { type: K }
        : { type: K; payload: T[K] };
};

interface PayloadTypes {
    SET_USER: { name: string; id: string };
    SET_THEME: "light" | "dark";
    LOGOUT: undefined;
}

type Actions = ActionMap<PayloadTypes>[keyof PayloadTypes];
// { type: "SET_USER"; payload: { name: string; id: string } }
// | { type: "SET_THEME"; payload: "light" | "dark" }
// | { type: "LOGOUT" }

function reducer(state: AppState, action: Actions): AppState {
    switch (action.type) {
        case "SET_USER":
            return { ...state, user: action.payload }; // payload is typed
        case "SET_THEME":
            return { ...state, theme: action.payload }; // "light" | "dark"
        case "LOGOUT":
            return { ...state, user: null };
    }
}

Builder Pattern with Chained Generics

// Type-safe query builder
type QueryResult<
    Selected extends string,
    Table extends string
> = Record<Selected, unknown> & { __table: Table };

class QueryBuilder<T extends string = never, Table extends string = string> {
    select<K extends string>(...cols: K[]): QueryBuilder<T | K, Table> {
        return this as any;
    }
    from<TName extends string>(table: TName): QueryBuilder<T, TName> {
        return this as any;
    }
    execute(): QueryResult<T, Table>[] {
        return [] as any;
    }
}

const results = new QueryBuilder()
    .select("name", "email")
    .from("users")
    .execute();
// QueryResult<"name" | "email", "users">[]

Frequently Asked Questions

What are TypeScript utility types?

TypeScript utility types are built-in generic types that transform existing types into new ones. They let you make properties optional (Partial), required (Required), read-only (Readonly), pick specific properties (Pick), omit properties (Omit), create dictionaries (Record), and extract or exclude union members (Extract, Exclude). They eliminate repetitive type definitions and keep your codebase DRY.

What is the difference between Pick and Omit in TypeScript?

Pick<T, K> creates a new type by selecting specific properties K from type T, while Omit<T, K> creates a new type by removing properties K from type T. They are complementary: Pick is better when you want a small subset of properties, and Omit is better when you want most properties but need to exclude a few. For example, Pick<User, "name" | "email"> keeps only name and email, while Omit<User, "password"> keeps everything except password.

How do I create custom utility types in TypeScript?

Custom utility types are built using mapped types, conditional types, and the infer keyword. A mapped type iterates over keys of a type using the [K in keyof T] syntax and transforms each property. Conditional types use the T extends U ? X : Y pattern to branch based on type relationships. The infer keyword extracts types from within other types. For example, type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] } makes all nested properties optional.

When should I use Record vs a regular interface in TypeScript?

Use Record<K, V> when you need a dictionary or map-like structure where all values share the same type, especially with dynamic or union keys. Use an interface when each property has a different type or when you need declaration merging. For example, Record<string, number> is ideal for a scores dictionary, while an interface is better for a User object with name (string), age (number), and active (boolean) properties.

What is the Awaited utility type in TypeScript?

Awaited<T> (TypeScript 4.5+) recursively unwraps Promise types to get the resolved value type. Awaited<Promise<string>> gives you string, and Awaited<Promise<Promise<number>>> gives you number. It is especially useful with ReturnType when working with async functions: type Result = Awaited<ReturnType<typeof fetchUser>> gives you the resolved return type of an async function rather than a Promise.

Related Resources

Keep learning: Utility types are one piece of the TypeScript puzzle. Our TypeScript Tips and Tricks guide covers const assertions, discriminated unions, branded types, and more patterns that pair perfectly with utility types. Use the TypeScript Types Cheat Sheet for a quick reference while coding.

Related Resources

TypeScript Tips and Tricks
15 practical patterns for cleaner TypeScript
TypeScript Types Cheat Sheet
Quick reference for all built-in types
JSON to TypeScript Converter
Generate interfaces from JSON data
JSON Formatter
Format and validate JSON for TypeScript APIs