RizTech Academy logo
RizTech Academy
TypeScript in Real ProjectsLesson 2 of 625 min

Strict mode, and why to turn it on from day one

The previous lesson called strict: true the most important line in tsconfig.json. This lesson explains why — what it actually turns on, what each check catches, and why the honest advice is to enable it from the first commit of a new project, not "later, when we have time". Strict mode is the difference between TypeScript that catches bugs and TypeScript that is JavaScript with extra ceremony.

What "strict" turns on

strict: true is not one check — it is a switch that enables a family of them at once. The important members:

Flag What it catches
noImplicitAny A parameter or variable TypeScript cannot infer, silently typed any
strictNullChecks Using a value that might be null or undefined without checking
strictFunctionTypes Passing a function whose parameter types are unsafe
strictPropertyInitialization A class field never assigned a value
strictBindCallApply bind/call/apply with wrong argument types
alwaysStrict Emits "use strict" and parses in strict mode
useUnknownInCatchVariables A caught error is unknown, not any

Two of these do the heavy lifting: noImplicitAny and strictNullChecks. They are the reason strict mode matters, so let us see each fail.

noImplicitAny — no silent any

Without it, a parameter TypeScript cannot infer is silently any, and all checking on it vanishes:

function total(items) {        // no annotation — items is implicitly `any`
  return items.reduce((a, b) => a + b, 0);
}
total("not an array");         // no error — and crashes at runtime

With noImplicitAny on, that same code is a compile error: "Parameter 'items' implicitly has an 'any' type" (TS7006). You are forced to annotate — items: number[] — and now the bad call is caught. The whole value of TypeScript is checking; a silent any is a hole in the net, and this flag closes it. This is the check people most often disable to "make the errors go away" — which is exactly backwards, because the errors are the point.

strictNullChecks — the billion-dollar flag

This is the single most valuable check in the language. Without it, null and undefined are assignable to everything, so the crash TypeScript should prevent sails through:

function greet(user: { name: string } | null) {
  return user.name.toUpperCase();   // no error WITHOUT strictNullChecks
}
greet(null);   // runtime crash: Cannot read properties of null

With strictNullChecks on, user.name is an error — "'user' is possibly 'null'" (TS18047) — until you narrow:

function greet(user: { name: string } | null) {
  if (user === null) return "Hello, guest";
  return user.name.toUpperCase();   // safe — narrowed
}

Every null-safety feature you have learnt — narrowing, ?., ??, the T | null type itself — only does anything with strictNullChecks on. Turn it off and TypeScript stops tracking null entirely; the "billion-dollar mistake" walks straight back in. This one flag is why TypeScript can honestly claim to prevent null crashes.

The other members, briefly

  • strictPropertyInitialization — a class field declared but never assigned is an error, catching the "I forgot to set it in the constructor" bug (paired with strictNullChecks).
  • useUnknownInCatchVariables — catch (e) types e as unknown, not any, forcing you to check what you caught before using it (the errors-typed lesson relied on this).
  • strictFunctionTypes / strictBindCallApply — close subtler holes in how function types and call/apply are checked. You rarely think about them, but they prevent real unsoundness.

You do not need to memorise each one. The point is that strict: true enables all of them together, and that is what you want.

Why "from day one" is not just a slogan

The strongest practical argument for strict mode is about timing. Turning it on for a new project costs nothing — there is no existing code to fix. Turning it on for a large existing codebase surfaces hundreds of errors at once, because every unhandled null and every implicit any that was always there suddenly becomes visible. That is not strict mode "breaking" things; it is strict mode revealing the bugs that were latent all along. But the volume is real, and it is why teams postpone it — and postponing it means every line written in the meantime is another line to fix later.

So the honest advice: enable strict before you write your first line. The migrating lesson covers how to adopt it gradually in an existing codebase (there are per-file and incremental strategies), because turning it on all at once on a mature project is genuinely disruptive. But for anything new, there is no reason to wait and every reason not to.

The anti-pattern: strict off, or strict "worked around"

Two failures look like TypeScript but are not:

  1. strict: false. The project compiles, the editor shows types, but nulls and implicit anys pass silently. You have the ceremony of TypeScript with none of the safety — arguably worse than plain JavaScript, because it looks checked. If you see a tsconfig with strict off or absent, that is the first thing to fix.
  2. Strict on, but defeated with any and !. A codebase that turns strict on and then sprinkles as any and ! to silence every error it produces has, in effect, turned it back off, one assertion at a time. The best-practices module's rules — prefer unknown, avoid assertions — are what keep strict mode meaningful rather than merely present.

Strict mode is necessary but not sufficient: it forces the compiler to check, and the rest of the course's discipline is what stops you from lying to it once it does.

Check your work

What strict: true is. A switch enabling a family of checks at once — not a single option.

The two that matter most. noImplicitAny (no silent any) and strictNullChecks (null/undefined tracked and must be handled).

What noImplicitAny catches. A parameter/variable TypeScript cannot infer, otherwise silently any — error TS7006, forcing an annotation.

What strictNullChecks catches, and why it is central. Using a possibly-null value without narrowing — error TS18047; every null-safety feature only works with it on.

Why "from day one". No existing code to fix on a new project; on a large existing one it surfaces every latent bug at once — real disruption, hence the migrating lesson's gradual path.

The two anti-patterns. strict: false (ceremony without safety), and strict-on-but-defeated with any/! (turning it off one assertion at a time).

Practice

  1. Set strict: false, write a function with an un-annotated parameter and a possibly-null access, and confirm neither errors. Turn strict: true on and watch both error (TS7006, TS18047).
  2. Fix the null error two ways: an early-return narrow, and ?. with a ?? default.
  3. Declare a class field and never assign it; with strictPropertyInitialization on, read the error, then fix it in the constructor.
  4. Write catch (e) { console.log(e.message) }; with useUnknownInCatchVariables on, note e is unknown and you must narrow. Fix it.
  5. Take a small plain-JavaScript file, rename it .ts, turn strict on, and count how many latent bugs surface. Reflect on what that number would be for a whole codebase.
  6. Find (or write) a strict-on file defeated by as any, and reason about whether it is safer than strict-off.

Official documentation

Next: declaration files — .d.ts, ambient types, and module augmentation.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship