Utility Types Deep Dive
Master built-in utility types and create your own
Built-in utility types solve common transformations. Understanding how they work lets you compose and create your own.
What you'll learn
Use Partial, Required, Pick, Omit, Record for object transformations
Apply Extract, Exclude, NonNullable for union filtering
Extract types with ReturnType, Parameters, Awaited
Compose multiple utility types together
Create custom utility types
Use string manipulation types (Uppercase, Capitalize, ...)
Object Shape Utilities
interface Todo {
title: string
description: string
completed: boolean
createdAt: Date
}
// Partial — all optional
type PartialTodo = Partial<Todo>
// Required — all required (removes ?)
type RequiredTodo = Required<PartialTodo>
// Pick — subset
type TodoPreview = Pick<Todo, "title" | "completed">
// { title: string; completed: boolean }
// Omit — exclude keys
type TodoWithoutDates = Omit<Todo, "createdAt">
// { title: string; description: string; completed: boolean }
// Record — key-value map
type PageMap = Record<string, Todo>
type WeekTodos = Record<"mon" | "tue" | "wed", Todo>
Union Filtering Utilities
type Status = "idle" | "loading" | "success" | "error" | 200 | 400 | 500
// Extract — pick members matching a type
type StringStatus = Extract<Status, string>
// "idle" | "loading" | "success" | "error"
// Exclude — remove members matching a type
type AsyncStatus = Exclude<Status, 200 | 400 | 500>
// "idle" | "loading" | "success" | "error"
// NonNullable — remove null and undefined
type Maybe = string | number | null | undefined
type Definitely = NonNullable<Maybe>
// string | number
Function Type Utilities
function createUser(name: string, age: number, admin?: boolean): User {
return { id: crypto.randomUUID(), name, age }
}
// ReturnType — what a function returns
type UserType = ReturnType<typeof createUser>
// User
// Parameters — tuple of parameter types
type CreateUserParams = Parameters<typeof createUser>
// [name: string, age: number, admin?: boolean]
// ConstructorParameters — for classes
class Logger {
constructor(
private prefix: string,
private level: string
) {}
}
type LoggerParams = ConstructorParameters<typeof Logger>
// [prefix: string, level: string]
// InstanceType — instance type from constructor
type LoggerInstance = InstanceType<typeof Logger>
// Logger
Awaited — Unwrap Promises
type AsyncData = Promise<Promise<string>>
type Data = Awaited<AsyncData>
// string (recursively unwraps)
// Real-world: infer API response types
async function fetchUser(id: string): Promise<{ name: string; email: string }> {
const res = await fetch(`/api/users/${id}`)
return res.json()
}
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>
// { name: string; email: string }
Before and After: Utilities in Action
interface FormValues {
name: string
email: string
age: number
subscribe: boolean
}
// Manual partial — fragile, verbose
interface PartialForm {
name?: string
email?: string
age?: number
subscribe?: boolean
}
// Manual error type — repetitive
type FormError = Partial<Record<keyof FormValues, string>>
Creating Custom Utility Types
// ByKeys — pick keys by value type
type ByKeys<T, V> = {
[K in keyof T]: T[K] extends V ? K : never
}[keyof T]
// Usage: pick keys whose values are strings
type StringKeys<T> = ByKeys<T, string>
interface Post {
id: number
title: string
slug: string
published: boolean
}
type PostStringKeys = StringKeys<Post>
// "title" | "slug"
// Spread — merge two types (second wins on conflict)
type Spread<A, B> = Omit<A, keyof B> & B
type Merged = Spread<{ a: string; b: number }, { b: string; c: boolean }>
// { a: string; b: string; c: boolean }
// Nullable — make any type null
type Nullable<T> = T | null
// AsyncReturnType — unwrap Promise from return type
type AsyncReturnType<T extends (...args: unknown[]) => unknown> = Awaited<
ReturnType<T>
>
String Manipulation Types
type Greeting = "helloWorld"
type Upper = Uppercase<Greeting> // "HELLOWORLD"
type Lower = Lowercase<Upper> // "helloworld"
type Capital = Capitalize<Greeting> // "HelloWorld"
type Uncap = Uncapitalize<Capital> // "helloWorld"
// Real-world: generated API action types
type Entity = "user" | "post" | "comment"
type Action = "create" | "read" | "update" | "delete"
type APIAction = `${Uppercase<Action>}_${Uppercase<Entity>}`
// "CREATE_USER" | "CREATE_POST" | "CREATE_COMMENT" | "READ_USER" | ...
Typed API Response Handler
Code
typescript
1interface APIResponse<T> {2data: T3meta?: {4 page: number5 totalPages: number6}7error?: { message: string; code: number }8}9 10// Create a partial update type for T11type UpdatePayload<T> = // use Partial and Pick12 13// Create a response for a list endpoint14type ListResponse<T> = // use Pick and Omit from APIResponse15 16// Try: list of users17interface User { id: number; name: string; email: string }18type UserList = ListResponse<User>[]Key Takeaways
Partial, Required, Pick, Omit, Record transform object shapes.
Extract, Exclude, NonNullable filter union members.
ReturnType, Parameters, Awaited extract from functions and promises.
Utility types compose together (e.g., Partial<Pick<T, K>>).
String manipulation types (Uppercase, Capitalize, ...) transform string literals.
Create custom utilities by combining mapped types, conditionals, and key remapping.