HiveCore Dev logo hivecore.dev

TypeScript strict Mode — What Each Flag Actually Catches

// essay · HiveCore Dev · 2026-05-09

TL;DR: strict turns on eight separate checks. Each catches a different category of bug. Knowing which is which makes you better at picking which to enable mid-migration.

strict is eight flags in a trench coat

Setting "strict": true in tsconfig.json enables eight separate strictness flags. You can enable them individually for incremental migration.

{
  "compilerOptions": {
    "strict": true
    // equivalent to:
    // "noImplicitAny": true,
    // "strictNullChecks": true,
    // "strictFunctionTypes": true,
    // "strictBindCallApply": true,
    // "strictPropertyInitialization": true,
    // "noImplicitThis": true,
    // "useUnknownInCatchVariables": true,
    // "alwaysStrict": true
  }
}

noImplicitAny — catches forgotten annotations

Without it, every parameter without an annotation silently becomes any. With it, you have to annotate or explicitly say : any — which makes review easy.

strictNullChecks — the big one

Forces null/undefined to flow through your types instead of hiding everywhere. Most of the bugs strict catches come from this single flag.

// without strictNullChecks
function greet(name: string) { return "hi " + name; }
greet(null); // compiles, runtime crashes

// with strictNullChecks
function greet(name: string) { return "hi " + name; }
greet(null); // type error

strictFunctionTypes — variance correctness

Makes function parameters checked contravariantly (correctly), not bivariantly. Catches a class of subtype-substitution bugs.

strictPropertyInitialization

Class fields must be assigned in the constructor or have a definite assignment hint (!:). Catches the 'I forgot to initialize' bug.

class User {
  name: string;       // error — not initialized
  age!: number;       // ok — definite assignment hint, you promise
  email = "";         // ok — has default
  constructor(name: string) {
    this.name = name;
  }
}

noImplicitThis

Catches this referring to any inside callbacks. Rare in modern code (arrow functions sidestep it), still useful as a guardrail.

useUnknownInCatchVariables

catch (e) defaults to unknown instead of any. Forces you to narrow before using the error — which catches 'oh I assumed it was Error' bugs.

try {
  await fetchUser();
} catch (e) {
  if (e instanceof Error) {
    log.error(e.message);
  } else {
    log.error("unknown error", e);
  }
}

Migration order for legacy codebases

If you're enabling strict in chunks: noImplicitAny first (mechanical fix), then strictNullChecks (the painful one), then the rest in any order. strictPropertyInitialization last — it usually requires class refactoring.

Related reading