Settings
Your save file

Progress lives in this browser, on this device. Download the save file to move it to another device, or to keep a copy in case this browser clears its storage.

Season 2
Week 10 of 12

Week 10 · Endgame

The Power Shift

Utility types

Partial, Pick, Omit, Record, ReturnType: types derived from types you already have.

Every big object in a real codebase eventually needs a second version of itself. An edit form only wants some fields optional. A public API response wants to drop the one field that is nobody else's business. A vote count wants a lookup table shaped like the object but is not really the object at all. The instinct that has served you fine in plain JS, just write a second interface by hand, is exactly the instinct that gets you two types quietly drifting apart the first time someone adds a field to the original and forgets the copy exists.

Utility types: functions, but for types

TypeScript ships a small set of built in utility types that take a type in and hand a new type back, the same verbs you already reach for on values. You already write pick these keys and drop that one on objects and arrays all the time, with .filter, with destructuring, with Object.entries. Utility types are that same instinct running at the type level instead of the value level, one type in, one derived type out, computed instead of retyped.

The ES6 you write

function applyNomineePatch(nominee, patch) {
  return { ...nominee, ...patch }
}

The TS version

interface Nominee {
  name: string
  age: number
  hometown: string
  strategy: string[]
}
 
type NomineePatch = Partial<Nominee>
 
function applyNomineePatch(nominee: Nominee, patch: NomineePatch): Nominee {
  return { ...nominee, ...patch }
}

What changed

applyNomineePatch's patch parameter goes from an untyped second argument to Partial<Nominee>, a type generated from Nominee itself rather than a second interface hand-written to match it. Nominee never gets copied anywhere. If a field gets added to Nominee tomorrow, NomineePatch already has it, automatically, with no edit required here.

Partial<Nominee> takes every property Nominee has and makes each one optional, same shape, every ? added automatically. Nobody wrote { name?: string; age?: number; ... } by hand. The type was computed from Nominee, once, and it stays computed forever: change Nominee, and NomineePatch changes with it on the next compile, no second edit anywhere.

Pick and Omit: API surfaces without the copy-paste

Omit and Pick solve the same drift problem from the other direction: instead of making every field optional, they choose which fields survive into a new type at all.

type PublicNomineeProfile = Omit<Nominee, 'strategy'>
// { name: string; age: number; hometown: string }
 
type NomineeCard = Pick<Nominee, 'name' | 'hometown'>
// { name: string; hometown: string }

Omit<Nominee, 'strategy'> reads as "everything Nominee has, except strategy," which is exactly the shape you want handing a houseguest's public profile back over an API without leaking their game strategy notes. Pick<Nominee, 'name' | 'hometown'> reads the opposite way, "only these fields, nothing else," useful anywhere you want a smaller view of a bigger interface, a summary card instead of the full record. Both stay in sync with Nominee automatically, same as Partial did.

Record: the maps you already write, typed

You have written objects used as lookup tables plenty of times, { Chelsie: 2, Angela: 1 }, key strings mapping to values, no interface anywhere because the keys are not fixed in advance. Record<K, V> types exactly that shape: any object where the keys are all type K and the values are all type V.

type VoteCounts = Record<string, number>
// any object shaped like { [voterChoice: string]: number }

That single line is the type for every "count things by name" object you have ever written without thinking about its type at all.

ReturnType: never write a function's output type twice

ReturnType<typeof someFunction> pulls a function's return type straight off the function itself, instead of you writing that same type out again by hand somewhere else in the file.

function scoreNominee(n: Nominee): number {
  return n.age + n.strategy.length
}
 
type NomineeScore = ReturnType<typeof scoreNominee>
// number, derived, not retyped

Change scoreNominee to return an object with a breakdown instead of a bare number, and NomineeScore updates itself on the next compile. No second type to remember, no risk of the copy going stale.

That is the whole lesson. Now prove it in the comp below.

Up next

Week 11 · Final HOH

The typed fetch wrapper

Locked until you win