The Problem These Solve
You've got an interface. It's used all over your app.
Then one day you need an "update user" function, and it turns out not every field is required for an update. Maybe someone's just changing their name. The naive move is to write a second, almost identical interface:
It works, but now you have two interfaces to keep in sync. Add a field to User next month, forget to add it to UpdateUserInput, and you've got a quiet bug waiting to happen.
Utility types exist so you never have to write that second interface by hand. They take a type you already have and reshape it, so the two stay connected automatically. Change User, and everything derived from it updates on its own.
Partial<T> Every Field, Now Optional
Partial takes a type and makes every property optional. That "update user" problem from above:
One line instead of a whole second interface. And if you add a phone field to User tomorrow, Partial<User> automatically includes it as an optional field, no extra work.
This is the one you'll reach for constantly: form state before submission, PATCH request bodies, anywhere you're building something up piece by piece instead of getting it all at once.
Pick<T, K> Only The Fields You Actually Want
Pick takes a type and a list of keys, and gives you back a new type with only those keys.
UserPreview only has id and name. Trying to add email to it would be a type error, and leaving out id or name would be too. It's exactly the shape you asked for, nothing more, nothing less.
This shows up constantly when a component or function only needs a slice of a bigger object. A user list item doesn't need the full User, it needs a name and maybe an avatar:
Now renderListItem can't accidentally depend on email or role, because those fields don't exist on the type it accepts. That's not just convenience, it's a guardrail that keeps a small component from quietly growing a dependency on data it was never meant to need.
Omit<T, K> Everything Except These Fields
Omit is Pick's mirror image. Instead of naming the fields you want, you name the ones you want to exclude, and keep everything else.
NewUserInput has everything User has except id, which makes sense, since the database assigns that when the row gets created. You never pass one in.
Omit and Pick solve the same kind of problem from opposite directions. Reach for Pick when the list of fields you want is short. Reach for Omit when it's the list of fields you don't want that's short. Either way, you're describing a shape by reference to a type you already have, instead of writing it out fresh and hoping it stays accurate.
Record<K, V> A Typed Map From Keys To Values
Record builds an object type where every key is of type K and every value is of type V.
TypeScript knows exactly two keys are allowed here, admin and member, because that's what the union type says. Try to add a guest key, or forget one of the two, and TypeScript will flag it.
That's a genuinely useful check. Without Record, a plain object literal wouldn't complain if you forgot a case, and you'd only find out at runtime when someone with the member role hit undefined behavior.
Record is also the cleaner way to write what you might otherwise reach for an index signature for:
Same result, but Record<string, number> reads like what it is: a map of keys to values.
Putting Them Together
These four aren't just useful alone, they combine well too. A common pattern is narrowing a type with Pick or Omit, then relaxing it with Partial for the cases where every field is optional.
Both CreateUserInput and UpdateUserInput are derived from User. Add a field to User, and both update automatically, no separate interfaces to remember to touch.
You can go further and build a lookup table keyed by ID, using Record and Pick together:
Bringing It All Together
Partial<T>makes every field on a type optional, ideal for update payloads and objects built up incrementally.Pick<T, K>keeps only the listed keys, useful when something only needs a small slice of a bigger type.Omit<T, K>keeps everything except the listed keys, useful when you're excluding one or two fields from an otherwise complete type.Record<K, V>builds an object type from a set of keys to a value type, and will catch you if you miss a case in a known, finite set of keys.- These combine naturally: narrow with
PickorOmit, then relax withPartial, all derived from one source type instead of duplicated by hand.
Once these four are part of how you think about shaping data, you'll stop writing a fresh interface every time you need a slightly different version of something you already defined. You'll reshape the one you have instead, and let TypeScript keep every version of it honest.
