DEV Community

Cover image for TypeScript Utility Types: Complete Guide to Partial, Required, Pick, Omit, Record, and More (2026)
Umesh Malik
Umesh Malik

Posted on • Edited on • Originally published at umesh-malik.com

TypeScript Utility Types: Complete Guide to Partial, Required, Pick, Omit, Record, and More (2026)

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), and ReturnType/Awaited (infer from real functions).
  • Union-level toolsExclude, Extract, NonNullable — reshape string/enum unions, not object keys. Mixing them up with Pick/Omit is 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.

',
description: 'Perfect for update flows where callers only send the fields they want to change.',
bullets: ['Form patches', 'Update DTOs', 'Feature-flagged config overrides'],
tone: 'success'
},
{
eyebrow: 'STRICTNESS',
title: '
Required',
description: 'Useful when a loose input shape becomes a guaranteed runtime shape after defaults or validation.',
bullets: ['Normalized config', 'Post-validation objects', 'Internal invariants'],
tone: 'info'
},
{
eyebrow: 'SHAPING',
title: '
PickandOmit',
description: 'The fastest way to carve focused view models and payload types out of larger domain types.',
bullets: ['Preview cards', 'Create/update payloads', 'Public vs internal fields'],
tone: 'warning'
},
{
eyebrow: 'MAPS',
title: '
Record',
description: 'Great for dictionaries, lookup tables, and keyed collections where the value shape is consistent.',
bullets: ['Role maps', 'Route registries', 'Status-to-label maps'],
tone: 'violet'
},
{
eyebrow: 'SAFETY',
title: '
ReadonlyandNonNullable',
description: 'These types help lock down accidental mutation and strip nullable cases after checks or normalization.',
bullets: ['Immutable config objects', 'Derived non-null props', 'Safer shared state'],
tone: 'success'
},
{
eyebrow: 'INFERENCE',
title: '
ReturnTypeandAwaited`',
description: 'Use them to extract shapes from real functions instead of manually duplicating types that will drift.',
bullets: ['Async loader results', 'Factory output types', 'API wrapper return values'],
tone: 'info'
}
]}
/>

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 Makes all properties optional Update / patch payloads
Required Makes all properties required Post-defaults / post-validation shapes
Readonly Makes all properties immutable Config you must not mutate
Pick Keeps only the keys in K Focused view models
Omit Removes the keys in K Create payloads (drop id)
Record Builds a K-to-T map Dictionaries, lookup tables
Exclude Removes U from a union T Narrowing string / enum unions
Extract Keeps only U from a union T Selecting union members
NonNullable Removes null and undefined After a null check
ReturnType Infers a function's return type Deriving result shapes
Parameters Infers a function's argument tuple Wrapping / forwarding calls
Awaited 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.

`typescript
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}

function updateUser(id: string, updates: Partial) {
// Only update the fields that were provided
}

updateUser('123', { name: 'Umesh' }); // Valid!
`

Required<T>

The opposite of Partial — makes all properties required.

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

const defaultConfig: Required = {
host: 'localhost',
port: 3000,
debug: false,
};
`

Pick<T, K>

Creates a type with only the specified properties.

`typescript
type UserPreview = Pick;

// Equivalent to:
// { id: string; name: string }
`

Omit<T, K>

Creates a type excluding the specified properties.

`typescript
type CreateUserInput = Omit;

// Everything except id
`

Record<K, T>

Creates a type with keys of type K and values of type T.

`typescript
type UserRoles = Record;

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:

`typescript
type Status = 'idle' | 'loading' | 'error';

const label: Record = {
idle: 'Ready',
loading: 'Working…',
error: 'Something broke',
}; // Drop one key and this won't compile
`

Exclude<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.

`typescript
type Role = 'admin' | 'editor' | 'viewer' | 'guest';

type StaffRole = Exclude;
// 'admin' | 'editor' | 'viewer'

type PrivilegedRole = Extract;
// '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:

`typescript
interface ApiResponse {
data: T;
status: number;
message: string;
}

type UserListResponse = ApiResponse[]>;
type UserUpdatePayload = Partial>;
`

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.

`typescript
interface FeatureFlags {
newSearch: boolean;
redesignedCheckout: boolean;
}

const flags: Readonly = {
newSearch: true,
redesignedCheckout: false,
};
`

NonNullable<T>

Strip null and undefined once you've validated a value.

typescript
type MaybeUser = User | null | undefined;
type SafeUser = NonNullable;

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.

`typescript
async function fetchCurrentUser() {
return { id: '123', name: 'Umesh', role: 'admin' as const };
}

type FetchCurrentUserResult = Awaited>;

function logEvent(name: string, payload: Record) {}
type LogEventArgs = Parameters;
// [name: string, payload: Record]
`

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.

typescript
type EventName = 'click' | 'hover' | 'focus';
type HandlerName =
on${Capitalize};
// 'onClick' | 'onHover' | 'onFocus'

NoInfer<T> (TypeScript 5.4+)

Added in TypeScript 5.4, NoInfer 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.

`typescript
function createState(initial: T, allowed: NoInfer[]) {
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:

  1. Confusing Omit/Pick with Exclude/Extract. Pick and Omit operate on the keys of an object type. Exclude and Extract operate on the members of a union. Using the wrong pair produces confusing errors.
  2. Expecting Partial 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.
  3. Omit doesn't validate keys against T in 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.
  4. Over-nesting. Partial>, 'x'>> is a puzzle, not a type. If a teammate has to decode it for 30 seconds, extract a named alias.
  5. Reaching for a utility type when you need runtime validation. Types vanish at compile time. NonNullable does not check anything at runtime — you still need the actual null check or a validator like Zod.

Key Takeaways

  • Use Partial for update operations where not all fields are required
  • Use Pick and Omit to create focused types from larger interfaces
  • Use Record for dictionary-like structures — with a union key, you get exhaustiveness checking
  • Use Exclude and Extract for unions; use Pick and Omit for object keys — don't mix them up
  • Readonly, NonNullable, ReturnType, Parameters, and Awaited cover 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

keeps only the keys you list, while Omit keeps everything except the keys you list. Use Pick when you want a small subset of a large type, and Omit when you want almost everything minus a field or two — like dropping id from a create payload.",
tag: 'Pick vs Omit'
},
{
question: 'What is the difference between Exclude and Omit?',
answer: "Omit removes keys from an object type; Exclude removes members from a union type. If you are working with an interface and want fewer properties, that is Omit. If you have a union like a set of string literals and want to drop one option, that is Exclude. Mixing them up is the most common utility-type mistake.",
tag: 'Exclude vs Omit'
},
{
question: 'Is Partial deep or shallow?',
answer: "Partial is shallow — it only makes the top-level properties optional. Nested objects keep their original required fields. If you need every level to be optional, you have to write your own recursive mapped type; TypeScript does not ship a built-in DeepPartial.",
tag: 'Gotcha'
},
{
question: 'Do TypeScript utility types affect runtime performance?',
answer: "No. All types, utility types included, are erased when TypeScript compiles to JavaScript. There is no runtime representation, no bundle-size cost, and no performance impact. The only cost is a slightly slower type-check during development if you nest them very deeply.",
tag: 'Performance'
},
{
question: 'When should I use Record instead of an index signature?',
answer: "Reach for Record when the set of keys is known and finite — especially a string-literal union — because you get exhaustiveness checking that flags a missing key at compile time. Use a plain index signature when keys are genuinely open-ended and arbitrary at runtime.",
tag: 'Record'
}
]}
/>

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


Originally published at umesh-malik.com

Keep reading on umesh-malik.com:

Top comments (0)