Lesson 40 of 80
The utility-types lesson showed you what Partial, Pick, and Omit do and briefly showed Partial's implementation. This one has you build all three yourself, line by line, so 'ordinary generics and mapped types' stops being something you're told and becomes something you can prove.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The utility-types lesson made the claim: Partial, Pick, and Omit aren't compiler magic, they're regular generics built from mapped types. That lesson showed you Partial's one-line implementation as proof. This lesson finishes the job — building Pick and Omit yourself, from the primitives you already have: keyof, mapped types, and (for Omit) Exclude, which you now also understand from the distributive-conditional-types lesson.
type MyPartial<T> = { [K in keyof T]?: T[K] };
As a reminder: iterate over every key K in T, keep the value type T[K], add ? to make it optional. This is the whole thing — no special case, no compiler hook.
interface User {
id: string;
name: string;
email: string;
age: number;
}
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type UserPreview = MyPick<User, "id" | "name">;
// { id: string; name: string }
K extends keyof T is the generic-constraints pattern from earlier in this track — it stops you from picking a key that doesn't actually exist on T. The mapped type itself is almost identical to Partial's, except it iterates over K (the keys you asked for) instead of keyof T (every key), and it doesn't add any modifier — it just copies each picked key's value type straight through.
type MyOmit<T, K extends keyof T> = MyPick<T, Exclude<keyof T, K>>;
type UserWithoutEmail = MyOmit<User, "email">;
// { id: string; name: string; age: number }
This is the interesting one, because it isn't a mapped type at all — it's a composition of two things you already have. Exclude<keyof T, K> takes every key of T and filters out the ones in K (using the distributive conditional type you already understand: it checks each key individually and drops the matches). What's left is "every key except the omitted ones" — and then MyPick does the actual work of building the resulting object type from that narrowed set of keys. Omit isn't a separate primitive; it's Pick and Exclude wired together.
MyPick's constraint, K extends keyof T, is doing real work, not decoration. Without it, you could call MyPick<User, "nonexistent"> and get a type with a bogus key instead of a compile error — the constraint is what makes the whole thing type-safe against typos.Omit from Pick and Exclude isn't a shortcut or a simplification — it's literally how TypeScript's own standard library defines it. Seeing Omit's real lib.d.ts source (type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>) after writing your own version yourself is one of the more satisfying "oh, that's really it" moments in this track.Required<T> is Partial<T>'s mirror image — same mapped type, but -? instead of ?, removing optionality instead of adding it: { [K in keyof T]-?: T[K] }. The - modifier syntax is the same idea as readonly and -readonly, just applied to the ? modifier instead.Pick/Omit/Partial in real code — it's that when a codebase needs a utility type the standard library doesn't ship (a DeepPick, a PartialBy<T, K> that makes only some keys optional), you now have the actual tools to build it instead of reaching for any."Implement Pick and Omit from scratch" is one of the most reliable ways an interviewer separates "has used TypeScript's utility types" from "understands the type system well enough to extend it." Being able to derive Omit as a composition of Pick and Exclude — rather than writing a redundant new mapped type from zero — also signals that you reach for composition over duplication, the same instinct that matters in ordinary application code.