AcademyTypeScript
Partial, Pick, Omit and the rest
Partial, Pick, Omit, Record, Required, Readonly and the rest — what each one is for, which one is unsafe, and how to stop re-declaring the same shape.
3 min readUpdated 3 exercises
One shape, many views
A typical entity is written once and then needed in four variations: the full row, the fields you may create, the fields you may update, the fields you may show. Writing those by hand means four places to edit and three chances to forget.
interface User {
id: string;
email: string;
password: string;
createdAt: Date;
}
type NewUser = Omit<User, 'id' | 'createdAt'>;
type UserUpdate = Partial<NewUser>;
type PublicUser = Omit<User, 'password'>;Every one of those follows User. That is the whole point.
The seven you will use constantly
| Type | Does | Note |
|---|---|---|
Partial<T> | every field optional | for updates and patches |
Required<T> | every field required | strips ? — including ones you wanted |
Readonly<T> | every field readonly | shallow, one level only |
Pick<T, K> | keep these keys | fails if a key does not exist |
Omit<T, K> | drop these keys | does not fail on a key that does not exist |
Record<K, V> | an object from keys to a value | Record<Role, boolean> |
ReturnType<F> | what a function gives back | pairs with typeof someFn |
Omit is the one that lies to you
Pick constrains its keys to keyof T. Omit does not — by design, so you can omit keys from a union. The cost is that a typo passes silently:
type PublicUser = Omit<User, 'passwrod'>;
// No error. And 'password' is still in there.That is a real leak, from a typo the compiler was willing to catch anywhere else. When the omitted field is one that must not escape, say it in a way that fails loudly:
type StrictOmit<T, K extends keyof T> = Omit<T, K>;
type PublicUser = StrictOmit<User, 'passwrod'>;
// Error: '"passwrod"' does not satisfy the constraint 'keyof User'.Three words of type, and the typo is a build failure again.
Readonly stops at the first level
const config: Readonly<{ db: { host: string } }> = { db: { host: 'a' } };
config.db = { host: 'b' }; // Error
config.db.host = 'b'; // Fine. Nothing stopped this.Readonly maps over the top-level keys and no further. For a nested structure you need a recursive version — and remember that all of it vanishes at runtime. If the object must actually be immutable while it is running, you want Object.freeze, applied recursively, as well as the type.
Exclude and Extract work on unions, not objects
These two are the ones people reach for on an object shape and get nothing from. They filter union members:
type Status = 'idle' | 'loading' | 'done' | 'failed';
type Settled = Exclude<Status, 'idle' | 'loading'>; // 'done' | 'failed'
type Pending = Extract<Status, 'idle' | 'loading'>; // 'idle' | 'loading'
type Defined<T> = Exclude<T, null | undefined>;NonNullable<T> is that last one, already in the standard library.
Composing them is the point
type Handlers = Record<
`on${Capitalize<Status>}`,
(result: PublicUser) => void
>;
// { onIdle: ..., onLoading: ..., onDone: ..., onFailed: ... }Each utility is a few lines of mapped or conditional type — nothing you could not write yourself, which is exactly why they are worth knowing: when one does not fit, you can write the one that does. Omit itself is Pick<T, Exclude<keyof T, K>>, and reading it that way is the moment the whole set stops being a list to memorise.
Now practice it
2 challenges · 1 quizReading this page is the cheap half. These are the exercises that make you use it.
- Utility Types — Partial & RequiredProPartial and Required over a real entity, which is where the create and update shapes come from.ChallengeSolve · EasyChallengeSolve · Easy
- Utility Types — Extract & ExcludeProFiltering union members rather than object keys — the distinction this page warns about.ChallengeSolve · MediumChallengeSolve · Medium
- Generics & Utility Types · Utility TypesUtility type questions, including the ones where Omit quietly accepts a key that does not exist.QuizTake · MediumQuizTake · Medium
Read next
Mapped and conditional types
How to write the utility types you have been importing: looping over keys, branching on a type, and pulling a type back out with infer.
4 min
Deriving types with keyof and typeof
The three operators that read types out of things you already wrote, so a type never has to be maintained next to the value it describes.
3 min