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 12 of 12

Reading time · 5 min

One comp · scored

Up next

Finale Night

Win this comp to reach the stage.

Week 12 · Endgame

Finale Night

Strict mode and the migration playbook

tsconfig anatomy and the file-by-file ratchet. Next stop: the real BBJ repo.

Finale night, and the prize is your real repo. Everything from move-in day to now has been building muscle memory on comps that mimic BBJ without any of the risk of touching it. This is the last lesson before that changes. What follows is the map for turning strict: false into strict: true across an actual production codebase without breaking it on day one, because that is the exact job waiting for you the moment you close this tab.

The tsconfig anatomy tour

strict in your tsconfig.json is not one flag, it is a bundle of several, all switched on at once. The two that matter most for everything you have learned this course:

  • noImplicitAny: every parameter and variable TypeScript cannot infer a type for must get an explicit one, or it is a compile error instead of a silent any. This is the single flag responsible for most of the red squiggles you have seen on starter code all course.
  • strictNullChecks: null and undefined stop being secretly assignable to every other type. Before this flag, let top: string could quietly hold undefined and TypeScript would never mention it. After it, a variable that might be missing has to say so in its own type, string | undefined, the exact shape you have been writing all comp long.

Two more flags worth knowing by name, outside the strict bundle:

  • allowJs: lets .ts and .js files live in the same project and get imported from each other. This is the flag that makes a file-by-file migration possible at all instead of an all-or-nothing rewrite.
  • noUncheckedIndexedAccess: this site's own build turns it on. It makes every indexed lookup, votes[hg], an array element, anything read by key or position, come back typed as T | undefined automatically, on the theory that you cannot actually prove that key exists just because the type says the value type is T. Plenty of real projects run without it and stay perfectly safe. The habit it enforces automatically, guarding a lookup with ?? fallback before trusting it, is worth having on purpose either way, which is exactly why this week's comp asks for it regardless of which flag your own project turns on.

Most of the rest of the flag list, exactOptionalPropertyTypes, verbatimModuleSyntax, and a handful of others, are real and occasionally useful, but they are not blocking anything on your migration. Leave them off until a specific problem makes you go looking for the flag that solves it.

The file-by-file ratchet, restated as a checklist

  1. npx tsc --init in the real repo. Start with allowJs: true and strict: false. Nothing breaks on day one, the whole app keeps running exactly as it does today.
  2. Convert two or three small, low-risk files first: date formatters, constants, small helpers. Nothing this course taught you skips out this early, but the annotations from week one alone already make these safer.
  3. Turn on noImplicitAny globally once those first files are clean. Fix what it finds, one file at a time. This is where narrowing, unions, and interfaces from the early weeks start earning their keep for real.
  4. Ratchet strict: true for new files as you write them, then for existing files as you touch them anyway during normal work. The migration rides along with regular season development from here, free of charge.
  5. The one rule that runs the whole way through, no exceptions: no any without a comment explaining why. It is the difference between a migration that actually finishes and a codebase that quietly stops learning anything six months in.

One more look at what compiles away

Every type this whole course has taught you is gone by the time the browser sees your code. Here is findWinner's cousin from the possibly-undefined section, side by side with what actually ships:

function leaderOf(names: string[], scoreboard: Record<string, number>): string | undefined {
  let best: string | undefined
  for (const name of names) {
    if (best === undefined || (scoreboard[name] ?? 0) > (scoreboard[best] ?? 0)) best = name
  }
  return best
}
function leaderOf(names, scoreboard) {
  let best
  for (const name of names) {
    if (best === undefined || (scoreboard[name] ?? 0) > (scoreboard[best] ?? 0)) best = name
  }
  return best
}

Every colon, every | undefined, every type parameter, stripped out, exactly like week one promised. What is left is the ?? fallback and the === undefined check, because those are real JavaScript doing real runtime work, not type annotations. That split is the whole idea to carry into the migration: types are a compile-time proof that your runtime checks are complete, they are never a substitute for writing the runtime check in the first place.

What ! costs

TypeScript has a shortcut for the exact situation this week's comp puts you in, a value the compiler thinks might be undefined that you are personally sure will not be. Write top! instead of proving it, and TypeScript stops checking that value entirely from that point on, no if, no ??, nothing. It compiles away to literally nothing at runtime too, same as every other type. If you are right, nothing changes. If you are wrong, and eventually on a long enough season you will be, the code that was supposed to catch it is gone, and you get a runtime crash on live data instead of a red squiggle at your desk. That is the entire cost of !: it spends strict mode's one job, proving a value is never missing, and replaces the proof with a promise. This comp bans it on purpose, because the honest fix, if (top === undefined) throw new Error('nobody won'), costs one extra line and actually holds up.

That is the whole lesson, and the whole course. Head to the finale.

Up next

Finale Night

The season recap and the real migration plan

Locked until you win