If your TypeScript config doesn’t run with strict: true, you’re not using TypeScript—you’re writing JavaScript with extra keystrokes and a false sense of safety. TypeScript can’t magically fix sloppy typing after the fact. The discipline has to start at the compiler. Turn on strict mode, embrace the friction, and watch your codebase get cleaner, faster to navigate, and dramatically easier to change.

What “strict” actually changes (and why it matters)

TypeScript’s strict flag is a shortcut for enabling a bundle of stricter type-checking behaviors. When it’s off, the compiler becomes permissive in ways that directly undermine TypeScript’s value: it allows missing or inconsistent types, tolerates nullable mistakes, and lets implicit any slip through your codebase.

With strict mode on, TypeScript forces you to be explicit about the boundaries of your program:

  • Is a value possibly null or undefined, or is it guaranteed?
  • Does a function accept the arguments you think it does?
  • Are you using a property that might not exist?
  • Are you returning a value of the type you claimed?

This isn’t academic. Those questions correlate closely to real runtime failures—especially null/undefined crashes and “shape drift” bugs where objects change and code keeps compiling anyway.

The “sprinkled any” problem: how teams sabotage TypeScript

Here’s the pattern I’ve seen over and over: teams start with strict: false, ship quickly, and convince themselves it’s fine. Then the codebase grows. Eventually you get this:

  • Developers add // @ts-ignore to silence errors.
  • Others introduce any because the compiler is “too strict.”
  • Functions accept values typed as any and cast their way through the mess.

Once any exists, TypeScript stops being a safety net. any is contagious: when a value is any, operations on it don’t get checked. You’ve effectively turned the compiler into a suggestion engine.

A typical example looks like this:

function saveUser(input: any) {
  // TypeScript can’t help; everything is allowed
  const email = input.email.toLowerCase();
  return fetch("/api/users", { method: "POST", body: JSON.stringify({ email }) });
}

When this runs, you’re trusting that input.email exists and is a string. Strict mode would force you to say what you accept—or validate it. That’s the difference between “works on my machine” and “works because it’s correct.”

With strict mode, you’d end up writing something like:

type SaveUserInput = { email: string };

function saveUser(input: SaveUserInput) {
  const email = input.email.toLowerCase();
  return fetch("/api/users", { method: "POST", body: JSON.stringify({ email }) });
}

Or if you’re dealing with untrusted data, you’d validate it at the boundary. Strict mode nudges you toward that healthier architecture: types at the edge, certainty inside.

Null safety: the bug you don’t want in production

If there’s one “entire class of failures” strict mode reliably reduces, it’s null reference errors—code that assumes a value exists when it doesn’t.

Consider this non-strict code:

function greet(user: { name?: string }) {
  return "Hello " + user.name.toUpperCase();
}

If name is optional, then user.name can be undefined. Without strict null checks, TypeScript may let this slide. In production, that becomes a runtime exception:

  • Cannot read properties of undefined (reading 'toUpperCase')

With strict mode enabled, you’re forced to handle that reality:

function greet(user: { name?: string }) {
  const name = user.name ?? "Anonymous";
  return "Hello " + name.toUpperCase();
}

Or if name must exist, the type should reflect that:

function greet(user: { name: string }) {
  return "Hello " + user.name.toUpperCase();
}

That’s the key: strict mode makes invalid assumptions unignorable. You either change the code, validate inputs, or tighten the types until the program matches the contract.

Function signatures and refactors: confidence comes from the compiler

Strict typing isn’t only about preventing runtime crashes. It also prevents slow, painful refactors.

When you change a function signature, strict mode helps ensure every call site updates correctly. Without strict mode, mismatches can compile and fail later—often in places you didn’t think to check.

Example:

function formatUser(user: { name: string; age: number }) {
  return `${user.name} (${user.age})`;
}

If a new team member starts calling it like this:

formatUser({ name: "Sam", age: "unknown" });

Strict mode will catch it immediately. Without strict mode, you can end up passing wrong types through the system, then debugging the symptoms later.

Even better, strict mode improves editor intelligence. With correct types everywhere, autocompletion and jump-to-definition become trustworthy. Onboarding improves because the codebase is readable: the types explain the shape of your system without forcing developers to memorize it.

The result is not just fewer bugs—it’s cheaper change.

Practical setup: turning strict mode on without panicking

“Yes, it’s harder. That’s the point.” Still, you don’t have to flip the switch and walk into a compile-error avalanche. The goal is to make the compiler your ally, not your enemy.

Start by checking your tsconfig.json. At minimum:

{
  "compilerOptions": {
    "strict": true
  }
}

If you’re currently off strict mode, the first build might produce a lot of errors. Resist the urge to spam any everywhere to “get green.” Instead:

  1. Fix the boundary errors first. Areas that ingest data from the outside world (API responses, form input, message queues, file parsing) should define clear types and perform validation.
  2. Add precise types instead of any. When you truly don’t know the type, use unknown and narrow it. unknown is strict; it forces you to check.
  3. Use narrowing properly. Optional fields and discriminated unions should be handled with if checks, in operators, or switch statements on tagged types.

For example, suppose you have an API response that may include different shapes:

type ApiResult =
  | { type: "success"; data: { email: string } }
  | { type: "error"; message: string };

function handleResult(result: ApiResult) {
  switch (result.type) {
    case "success":
      return result.data.email;
    case "error":
      throw new Error(result.message);
  }
}

Strict mode will help you maintain exhaustiveness. That’s how you prevent “new variant arrives and nothing updates” bugs.

If you want a migration strategy that’s actually humane, consider tightening one strictness area at a time, but keep the long-term destination clear: you want the full strict: true eventually. Half-measures tend to become permanent.

When strict mode is painful: the right kind of pain

Strict TypeScript isn’t about being precious. It’s about forcing your code to earn its correctness.

If strict mode flags dozens of issues, that’s not “the compiler being annoying.” It’s the compiler pointing at places where your program currently has implicit assumptions. Those assumptions might not fail today—but they will fail eventually, and usually during the most inconvenient moment: a refactor, a deploy, an edge-case input, or a new data shape.

So what do you do when strict mode is painful?

  • Stop hiding behind any. Replace it with unknown and narrow.
  • Don’t weaken types just to satisfy the compiler. If you lie to TypeScript, you’ll pay later.
  • Make nullability explicit. If a value can be absent, encode that in the type.
  • Treat types as documentation. Your future self will thank you.

If you’re building a real product, strictness is a form of respect—for users, for teammates, and for the person who inherits the code next.

Conclusion: strict mode is the default your future needs

Turning on TypeScript strict mode is the simplest high-leverage decision you can make in a JavaScript-to-TypeScript world. It catches null reference errors, enforces correct function signatures, and prevents a flood of silent any that erodes TypeScript’s value. The friction up front buys you reliability, onboarding speed, and refactor confidence that doesn’t rely on vibes.

So enable strict: true. Don’t “maybe later” it. If your codebase isn’t ready for strict mode, that’s your real roadmap—not an excuse to keep writing JavaScript with a TypeScript wrapper.