Right, let’s talk about strict. You’ve probably seen it in a tsconfig.json file and wondered if it’s just a glorified “make my life harder” button. It’s not. It’s more like your brilliant but slightly pedantic friend who insists you wear a helmet while biking. Annoying? Sometimes. Life-saving? Absolutely.

Think of strict not as a single rule, but as a curated bundle of other, more specific rules. It’s the master switch. When you set "strict": true in your tsconfig.json, you’re not enabling one thing; you’re enabling a whole family of incredibly useful, sanity-preserving options. The most famous of these are strictNullChecks and strictFunctionTypes, but the family is large and growing. The core philosophy is simple: turn on as many of these high-confidence checks as possible to catch bugs before they become runtime mysteries.

The beautiful part is you don’t have to go all-in. You can enable the umbrella strict flag to get the recommended baseline of safety and then, if you absolutely must, disable one or two of its children that are causing you grief in a legacy codebase. But I’ll be honest with you: if you’re disabling a child of strict, you’re usually papering over a code smell rather than solving a real problem.

What’s Actually in the Box?

So what does "strict": true actually enable? As of TypeScript v5.x, it includes these flags (and the list might grow in future versions, which is a good thing):

  • noImplicitAny
  • noImplicitThis
  • strictNullChecks
  • strictBindCallApply
  • strictFunctionTypes
  • strictPropertyInitialization
  • useUnknownInCatchVariables (added in v4.4)
  • …and a few others.

Let’s be clear: the designers got this one right. Bundling these together is a fantastic default. It prevents the “paradox of choice” where you’re faced with 20 flags and no idea which ones are important. They’ve done the hard work of curating the essential set for you.

A Tale of Two Codebases

Let me show you the stark difference. Here’s TypeScript on its default, lax mode. It’s… not great.

// Without `strict: true` (or more specifically, without `strictNullChecks`)
function calculateArea(radius) { // `radius` is implicitly 'any'. Yikes.
  return Math.PI * radius * radius;
}

let result = calculateArea(null); // This is allowed. It will return NaN.
console.log(result); // NaN. Enjoy debugging that.

Now, let’s see the same code with strict mode holding our hand, nay, forcing our hand to write better code.

// With `strict: true` (which enables `strictNullChecks` and `noImplicitAny`)
function calculateArea(radius: number) { // We MUST declare a type now.
  return Math.PI * radius * radius;
}

let result = calculateArea(null); // Compiler Error: Argument of type 'null' is not assignable to parameter of type 'number'.
// ^^ This error is a gift. It's a free bug fix, delivered instantly.

See the difference? The first example fails silently at runtime. The second example fails noisily at compile time, right under your nose, before you even save the file. This is the core value proposition of strict mode.

The One Legitimate Gripe: strictPropertyInitialization

This is the one rule where the designers’ choices can feel a bit… academic. strictPropertyInitialization requires that you initialize class properties in the constructor, or guarantee that they’re initialized via a method you call in the constructor. It’s a good rule! It prevents you from accessing undefined. But it has a famously annoying interaction with dependency injection.

class UserService {
  private repository: UserRepository; // Error: Property 'repository' has no initializer and is not definitely assigned in the constructor.

  constructor() {
    // The repository is injected by a framework (e.g., NestJS, Angular) *after* construction.
    // So we can't initialize it here the normal way.
  }
}

The compiler is technically correct. It has no way of knowing your framework’s magic. The solution isn’t to disable the rule, it’s to use the Definite Assignment Assertion Operator (!), a tool specifically designed for this scenario. It’s you telling the compiler, “I, a responsible adult, promise this will be assigned before anything uses it.”

class UserService {
  private repository!: UserRepository; // Note the '!' bang operator.

  constructor() {
    // Framework magic happens later. I pinky-swear.
  }
}

Use ! sparingly and with guilt. It’s a necessary escape hatch for these specific scenarios, not a way to silence the compiler for your own sloppy code.

The Verdict

You should always use "strict": true. Full stop. Start every new project with it. For existing projects, turn it on and embrace the wave of errors. Fix them in chunks. It’s not just a configuration option; it’s a qualitative shift in what TypeScript is. Without it, you’re playing with a superset of JavaScript that adds types as a suggestion. With it, you’re using a truly statically typed language that will actively prevent whole categories of bugs. It transforms TypeScript from a fancy linter into a powerful correctness tool. And that’s the whole point, isn’t it?