Utility Types
IntermediatePartial, Pick, Omit, Record, ReturnType and friends. Deriving types from other types is what keeps them in sync when the source changes.
Overview
The habit that separates comfortable TypeScript from tedious TypeScript is deriving rather than duplicating. If a form edits a subset of User, that form's type should be computed from User, so that renaming a field breaks the form at compile time instead of quietly diverging. The built-in utility types cover almost every derivation you need day to day, and knowing the list well is more valuable than being able to write exotic conditional types.
The Ones You Use Daily
Modifying and selecting from an existing object type.
interface User {
id: number
name: string
email: string
role: 'student' | 'admin'
}
Partial<User> // every field optional — PATCH payloads
Required<User> // every field required
Readonly<User> // every field readonly
Pick<User, 'id' | 'name'> // { id, name }
Omit<User, 'id'> // everything except id — creation payloads
Record<'easy' | 'hard', number> // { easy: number; hard: number }
// Realistic use
type CreateUser = Omit<User, 'id'>
type UpdateUser = Partial<Omit<User, 'id'>>
type UserCard = Pick<User, 'name' | 'role'>
// Rename a field on User and every one of these updates with it.
// Hand-written duplicates would silently drift.Unions and Functions
Filtering unions, and extracting types out of functions and promises.
type Status = 'idle' | 'loading' | 'success' | 'error'
Exclude<Status, 'idle'> // 'loading' | 'success' | 'error'
Extract<Status, 'success' | 'error'> // 'success' | 'error'
NonNullable<string | null> // string
// Extracting from functions — very useful for inferred sources
function getConfig() { return { retries: 3, timeout: 5000 } }
type Config = ReturnType<typeof getConfig> // { retries: number; timeout: number }
type Args = Parameters<typeof getConfig>
type Resolved = Awaited<ReturnType<typeof fetchUser>> // unwraps the Promise
// typeof on a value gives its type — the bridge between the two worlds
const DEFAULTS = { page: 1, size: 20 } as const
type Defaults = typeof DEFAULTS // { readonly page: 1; readonly size: 20 }
type DefaultKey = keyof typeof DEFAULTS // 'page' | 'size'as const
Freezes a literal into its narrowest type. It is how you turn a constant array into a union.
const levels = ['easy', 'medium', 'hard']
// type: string[] — the literals are lost
const levels2 = ['easy', 'medium', 'hard'] as const
// type: readonly ['easy', 'medium', 'hard']
type Level = typeof levels2[number] // 'easy' | 'medium' | 'hard'
// One source of truth for both the runtime list and the type:
levels2.forEach(...) // iterate at runtime
function setLevel(l: Level) { } // and the union is derived from it
// This replaces the enum for most cases, and unlike an enum it
// compiles to nothing extra and interops cleanly with plain JSON.Key Points to Remember
- 1Derive types from a single source rather than duplicating shapes — renames then break at compile time
- 2Partial, Pick, Omit and Record cover most everyday derivations; Omit<User, "id"> is the creation payload
- 3ReturnType, Parameters and Awaited extract types out of existing functions and promises
- 4typeof on a value yields its type, bridging the value world and the type world
- 5as const preserves literals, and typeof arr[number] turns a constant array into a union — usually better than an enum
Interview Questions
Sign in to ask AriaHow would you type the payload for creating a user when you already have a User interface?
What does `as const` do, and how do you derive a union type from a constant array?
Why derive types with Pick and Omit instead of writing the shapes out?
Ask Aria about Utility Types
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.