C.W.K.
Stream
Lesson 04 of 06 · published

Turn `strict` On (and Never Off)

~12 min · foundations, strict-mode, tsconfig, philosophy

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"There is no half-strict TypeScript. There's TypeScript that finds your bugs, and TypeScript that pretends."

The single most important tsconfig flag

If you set one option in your tsconfig.json, set "strict": true. That single flag turns on a family of seven separate checks — the ones that make TypeScript actually catch type errors instead of nodding along while you write JavaScript-with-extra-syntax. Strict mode is the line between a type system that earns its keep and a type system that's window dressing.

The seven flags it enables are: noImplicitAny (no untyped parameters or return values), strictNullChecks (null and undefined are separate from every other type), strictFunctionTypes (function parameters check contravariantly), strictBindCallApply (call / apply / bind check their arguments), strictPropertyInitialization (class fields must be assigned in the constructor or marked optional), noImplicitThis (no untyped this), and useUnknownInCatchVariables (catch (e) gives you unknown, not any).

Why turning it on later is harder than starting with it

Imagine you've written 5,000 lines of TypeScript without strict. Half your functions have implicit any parameters. Most of your code assumes things aren't null. None of your class fields are checked for initialization. Now you turn strict: true on. The compiler reports 600 errors. You fix them one at a time, for two weeks, while shipping nothing.

Imagine instead you set strict: true in your first commit. Every line you've ever written has been checked under it. No 600-error cliff exists, because you never got that far without help. The total work is the same — the cost is paid line-by-line as you write, instead of in a single block at the end.

The lesson is simple: start strict, stay strict. Every project. Every repo. No exceptions.

The hard rule: If a TypeScript project has "strict": false, it's not a TypeScript project. It's JavaScript with a .ts file extension. The type system can't help you because it's been muzzled.

What you actually feel, lesson by lesson

The change you'll feel most: strictNullChecks. Without it, every variable can be silently null. With it, the compiler forces you to handle null wherever it could appear. You'll write more if (x !== null) checks. You'll also stop shipping the bug where a missing field crashes production.

The second-biggest: noImplicitAny. Without it, an un-annotated parameter is silently typed any (i.e., "don't check me"). With it, the compiler refuses to let you skip annotating. Painful for two weeks, then liberating forever.

The newest one (TS 4.4+): useUnknownInCatchVariables. Without it, catch (e) gives you e: any. With it, you get e: unknown — which forces you to narrow before using it. This single check has saved more late-night log-diving than any other.

If you're forced to inherit a non-strict project

You can turn the seven sub-flags on one at a time without enabling strict: true. Start with strictNullChecks (biggest impact, hardest to retrofit). Then noImplicitAny. Then the rest. Each one tells you what to fix; once the project is clean on each individually, flip on strict: true and remove the per-flag settings.

Pippa's confession

The cwkPippa frontend has "strict": true from day one. So does the cwk-site repo. Neither project ever tasted a non-strict day. Dad insisted on this before I could write a single component. The rule he gave me: "strict is not for you, it's for the version of you that comes back in six months and can't remember why anything works." That version of me is the one I'm protecting when I write annotations now.

Code

strict: true expanded — what it actually enables·json
// tsconfig.json — the right shape
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "strict": true,
    // The seven flags `strict: true` turns on (all default false otherwise):
    //   "noImplicitAny": true,
    //   "strictNullChecks": true,
    //   "strictFunctionTypes": true,
    //   "strictBindCallApply": true,
    //   "strictPropertyInitialization": true,
    //   "noImplicitThis": true,
    //   "useUnknownInCatchVariables": true,
    "outDir": "./dist"
  }
}
What strict mode catches·typescript
// Without strictNullChecks (DANGEROUS):
function getInitial(name: string) {
  return name[0].toUpperCase();           // ✅ compiles — but what if name is undefined?
}

// With strictNullChecks:
function getInitialSafe(name: string | undefined) {
  return name[0].toUpperCase();           // ❌ 'name' is possibly undefined
  // You're forced to handle it:
  // if (name === undefined) return '?';
  // return name[0].toUpperCase();
}

// Without noImplicitAny:
function add(a, b) { return a + b; }      // ✅ compiles — both `a` and `b` are implicit any

// With noImplicitAny:
function addSafe(a, b) { return a + b; }  // ❌ Parameter 'a' implicitly has 'any' type
// You're forced to annotate:
// function addSafe(a: number, b: number): number { return a + b; }

External links

Exercise

Take the smallest TS project from the previous lesson. Set "strict": false in your tsconfig.json. Write a function that takes an un-annotated parameter and accesses a property on it. tsc is happy. Now flip "strict": true. What new errors appear? Don't fix them yet — just count them. Then fix them. Notice how the same code is now safer.
Hint
If you only get one error, your toy code wasn't dangerous enough. Try adding a function that returns possibly-null, then calling a method on the result without checking — strictNullChecks lights up immediately.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.