TypeScript Utility Types: Complete Guide to Partial, Required, Pick, Omit, Record, and More (2026)
TypeScript utility types explained: Partial, Pick, Omit, Record, Exclude, ReturnType and more — with real examples, a cheat sheet, and common pitfalls.

TypeScript utility types are built-in generic types that transform an existing type into a new one — making fields optional, selecting a subset of properties, building lookup maps, or extracting a function’s return type — without you redefining anything by hand. They ship with the compiler, cost nothing at runtime, and are the fastest way to kill type duplication in a real codebase.
I reach for them every day on large frontend and API code. This guide covers every utility type you’ll actually use, with real examples, a cheat sheet, and the mistakes that trip people up. They pair naturally with the ES2024 features I actually use and my frontend testing strategies. See also Node.js Backend for Frontend Developers.
TL;DR
- Utility types derive new types from existing ones so a single source of truth drives your whole type graph — change the interface once, everything downstream follows.
- The five you’ll use most:
Partial(all optional),Pick/Omit(carve out fields),Record(typed maps), andReturnType/Awaited(infer from real functions). - Union-level tools —
Exclude,Extract,NonNullable— reshape string/enum unions, not object keys. Mixing them up withPick/Omitis the #1 mistake. - They’re zero-cost. Every utility type is erased at compile time. There is no runtime bundle or performance impact, ever.
- Don’t over-nest. A three-deep utility chain is harder to read than a named type. Reach for built-ins to remove duplication, not to show off.
What Are TypeScript Utility Types?
A TypeScript utility type is a built-in generic type that takes an existing type and produces a new, transformed one — for example making every field optional, keeping only a subset of properties, or extracting a function’s return type. They’re globally available with no import, they’re built from mapped and conditional types under the hood, and they live purely at the type level, so they vanish the moment your code compiles to JavaScript.
UTILITY TYPES YOU'LL REACH FOR CONSTANTLY
A small subset of built-ins covers most day-to-day type reshaping in frontend and API work. Knowing when to use each one removes a lot of duplication.
PATCHES
`Partial<T>`
Perfect for update flows where callers only send the fields they want to change.
- Form patches
- Update DTOs
- Feature-flagged config overrides
STRICTNESS
`Required<T>`
Useful when a loose input shape becomes a guaranteed runtime shape after defaults or validation.
- Normalized config
- Post-validation objects
- Internal invariants
SHAPING
`Pick<T, K>` and `Omit<T, K>`
The fastest way to carve focused view models and payload types out of larger domain types.
- Preview cards
- Create/update payloads
- Public vs internal fields
MAPS
`Record<K, T>`
Great for dictionaries, lookup tables, and keyed collections where the value shape is consistent.
- Role maps
- Route registries
- Status-to-label maps
SAFETY
`Readonly<T>` and `NonNullable<T>`
These types help lock down accidental mutation and strip nullable cases after checks or normalization.
- Immutable config objects
- Derived non-null props
- Safer shared state
INFERENCE
`ReturnType<T>` and `Awaited<T>`
Use them to extract shapes from real functions instead of manually duplicating types that will drift.
- Async loader results
- Factory output types
- API wrapper return values
Why TypeScript Utility Types Matter
When building large-scale applications like the ones I work on at Expedia Group, type safety isn’t just nice to have — it’s essential. Utility types help you derive new types from existing ones without duplication.
The real win is a single source of truth. Define your User interface once, then derive the create payload, the update payload, the preview card, and the API response from it. When the User shape changes, every derived type updates automatically and the compiler shows you exactly what broke. Hand-written duplicate types drift the moment someone forgets to update one of them.
TypeScript Utility Types Cheat Sheet
Here’s every utility type in this guide at a glance — bookmark this table.
| Utility type | What it does | Reach for it when |
|---|---|---|
Partial<T> | Makes all properties optional | Update / patch payloads |
Required<T> | Makes all properties required | Post-defaults / post-validation shapes |
Readonly<T> | Makes all properties immutable | Config you must not mutate |
Pick<T, K> | Keeps only the keys in K | Focused view models |
Omit<T, K> | Removes the keys in K | Create payloads (drop id) |
Record<K, T> | Builds a K-to-T map | Dictionaries, lookup tables |
Exclude<T, U> | Removes U from a union T | Narrowing string / enum unions |
Extract<T, U> | Keeps only U from a union T | Selecting union members |
NonNullable<T> | Removes null and undefined | After a null check |
ReturnType<T> | Infers a function’s return type | Deriving result shapes |
Parameters<T> | Infers a function’s argument tuple | Wrapping / forwarding calls |
Awaited<T> | Unwraps a Promise | Async return values |
Uppercase / Lowercase / Capitalize | Transform string-literal types | Typed keys and event names |
Partial<T>
Makes all properties of T optional. This is incredibly useful for update functions.
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
function updateUser(id: string, updates: Partial<User>) {
// Only update the fields that were provided
}
updateUser('123', { name: 'Umesh' }); // Valid!Required<T>
The opposite of Partial — makes all properties required.
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
const defaultConfig: Required<Config> = {
host: 'localhost',
port: 3000,
debug: false,
};Pick<T, K>
Creates a type with only the specified properties.
type UserPreview = Pick<User, 'id' | 'name'>;
// Equivalent to:
// { id: string; name: string }Omit<T, K>
Creates a type excluding the specified properties.
type CreateUserInput = Omit<User, 'id'>;
// Everything except idRecord<K, T>
Creates a type with keys of type K and values of type T.
type UserRoles = Record<string, User[]>;
const roleMap: UserRoles = {
admin: [/* admin users */],
editor: [/* editor users */],
};Record shines when the key is itself a union — you get exhaustiveness for free. Miss a key and the compiler complains:
type Status = 'idle' | 'loading' | 'error';
const label: Record<Status, string> = {
idle: 'Ready',
loading: 'Working…',
error: 'Something broke',
}; // Drop one key and this won't compileExclude<T, U> and Extract<T, U>
These two work on union types, not object keys — this is the distinction most people miss. Exclude removes members from a union; Extract keeps only the members that match.
type Role = 'admin' | 'editor' | 'viewer' | 'guest';
type StaffRole = Exclude<Role, 'guest'>;
// 'admin' | 'editor' | 'viewer'
type PrivilegedRole = Extract<Role, 'admin' | 'editor'>;
// 'admin' | 'editor'If you find yourself typing out a union by hand that’s “the big union minus one option,” that’s an Exclude.
Practical Example: API Response Types
Here’s how I combine these utility types in real projects:
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type UserListResponse = ApiResponse<Pick<User, 'id' | 'name' | 'role'>[]>;
type UserUpdatePayload = Partial<Omit<User, 'id'>>;That last line reads exactly like the business rule: “an update can change any field except the id.” That’s the goal — types that document intent.
A Few More Utility Types Worth Knowing
Readonly<T>
Use Readonly when an object should never be mutated after creation.
interface FeatureFlags {
newSearch: boolean;
redesignedCheckout: boolean;
}
const flags: Readonly<FeatureFlags> = {
newSearch: true,
redesignedCheckout: false,
};NonNullable<T>
Strip null and undefined once you’ve validated a value.
type MaybeUser = User | null | undefined;
type SafeUser = NonNullable<MaybeUser>;ReturnType<T>, Parameters<T>, and Awaited<T>
Infer shapes from real functions instead of duplicating them by hand. ReturnType grabs the return type, Parameters grabs the argument tuple, and Awaited unwraps a promise.
async function fetchCurrentUser() {
return { id: '123', name: 'Umesh', role: 'admin' as const };
}
type FetchCurrentUserResult = Awaited<ReturnType<typeof fetchCurrentUser>>;
function logEvent(name: string, payload: Record<string, unknown>) {}
type LogEventArgs = Parameters<typeof logEvent>;
// [name: string, payload: Record<string, unknown>]Parameters is a lifesaver when you’re wrapping a third-party function and want your wrapper to accept exactly the same arguments — no manual duplication that drifts on the next library upgrade.
String Manipulation Types: Uppercase, Lowercase, Capitalize
These transform string-literal types at the type level — handy for deriving typed event names or object keys from a base union.
type EventName = 'click' | 'hover' | 'focus';
type HandlerName = `on${Capitalize<EventName>}`;
// 'onClick' | 'onHover' | 'onFocus'NoInfer<T> (TypeScript 5.4+)
Added in TypeScript 5.4, NoInfer<T> blocks a type parameter from being inferred at a specific position — useful when you want one argument to constrain the generic rather than widen it.
function createState<T>(initial: T, allowed: NoInfer<T>[]) {
return { initial, allowed };
}
// `allowed` must match the type inferred from `initial`,
// instead of widening T to the union of both arguments.Common Mistakes with Utility Types
A few pitfalls I see constantly in code review:
- Confusing
Omit/PickwithExclude/Extract.PickandOmitoperate on the keys of an object type.ExcludeandExtractoperate on the members of a union. Using the wrong pair produces confusing errors. - Expecting
Partial<T>to be deep. It’s shallow — it only makes top-level properties optional. Nested objects keep their original required fields. For deep optionality you need a recursive mapped type of your own. Omitdoesn’t validate keys againstTin older setups. Passing a key that doesn’t exist on the type used to fail silently. Keep your TypeScript version current so typos in the omit list are caught.- Over-nesting.
Partial<Omit<Record<string, Pick<User, 'id'>>, 'x'>>is a puzzle, not a type. If a teammate has to decode it for 30 seconds, extract a named alias. - Reaching for a utility type when you need runtime validation. Types vanish at compile time.
NonNullable<T>does not check anything at runtime — you still need the actual null check or a validator like Zod.
USE BUILT-INS WHERE THEY HELP READABILITY
Utility types are powerful because they let you derive types from the real source of truth. They are not a license to make every type declaration cryptic.
REACH FOR THEM
Built-ins are the right move when they remove duplication
- Deriving create/update payloads from domain types
- Shaping list-item or card-view models from larger objects
- Extracting async return values from real functions
- Modeling keyed maps and configuration registries
PULL BACK
Prefer a named custom type when clarity starts dropping
- Nested utility chains are harder to read than a simple alias
- Domain concepts deserve names, not just transformations
- Type cleverness does not replace runtime validation
- If teammates need to mentally parse the type for 30 seconds, simplify it
Key Takeaways
- Use
Partialfor update operations where not all fields are required - Use
PickandOmitto create focused types from larger interfaces - Use
Recordfor dictionary-like structures — with a union key, you get exhaustiveness checking - Use
ExcludeandExtractfor unions; usePickandOmitfor object keys — don’t mix them up Readonly,NonNullable,ReturnType,Parameters, andAwaitedcover a lot of everyday type work- Combine utility types for complex transformations, but stop before they get cryptic
- These types are zero-cost at runtime — they only exist during compilation
FAQ
Questions readers usually have
The questions I get most often about TypeScript utility types.
Conclusion
Utility types are the highest-leverage feature in everyday TypeScript: they turn one interface into a whole family of precise, self-updating types with zero runtime cost. Master the object-key tools (Partial, Pick, Omit, Record) and the union tools (Exclude, Extract, NonNullable), keep your chains shallow, and remember that types don’t validate data at runtime.
If this was useful, the same type-safety mindset runs through my framework breakdown in SvelteKit vs Next.js and the ES2024 features I actually use next.
Sources
- TypeScript Handbook — Utility Types — the official reference for every built-in utility type.
- TypeScript 5.4 Release Notes — introduces
NoInfer<T>.
Related Articles

Web Engineering
Node.js Backend for Frontend Developers: Express, REST APIs, Auth, and Deployment (Step-by-Step)
A frontend developer's guide to building backends with Node.js: Express, REST APIs, middleware, databases, auth, and deployment — plus the mindset shift.

Web Engineering
Frontend Testing Strategies That Actually Work in 2026
A pragmatic guide to frontend testing in 2025: component tests, integration, E2E strategies, and the patterns that give the most confidence per line.

Web Engineering
JavaScript ES2024: The Features You'll Actually Use
The most impactful ES2024 features: Array grouping, Promise.withResolvers, well-formed Unicode strings, and the RegExp v flag — with practical examples.
Keep reading
Get new posts on AI, Claude Code & LLMs
New deep-dives on AI engineering, Claude Code, and developer tooling — follow along however you prefer.
About the Author
Software engineer writing about AI, Claude Code, LLMs, OpenAI, Anthropic, and developer tooling. 5+ years building production systems at Expedia Group, Tekion, and BYJU'S.