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 silentany. This is the single flag responsible for most of the red squiggles you have seen on starter code all course.strictNullChecks:nullandundefinedstop being secretly assignable to every other type. Before this flag,let top: stringcould quietly holdundefinedand 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.tsand.jsfiles 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 asT | undefinedautomatically, on the theory that you cannot actually prove that key exists just because the type says the value type isT. Plenty of real projects run without it and stay perfectly safe. The habit it enforces automatically, guarding a lookup with?? fallbackbefore 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
npx tsc --initin the real repo. Start withallowJs: trueandstrict: false. Nothing breaks on day one, the whole app keeps running exactly as it does today.- 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.
- Turn on
noImplicitAnyglobally 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. - Ratchet
strict: truefor 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. - The one rule that runs the whole way through, no exceptions: no
anywithout 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.