You can write TypeScript that compiles cleanly and still crashes at runtime with “Cannot read properties of null.” If that happens, you probably have strict: false in your tsconfig, or you never set it at all.

Strict mode is not a preference. It is the difference between TypeScript actually working and TypeScript being a syntax game.

What Strict Mode Actually Enables

strict: true is a shorthand that toggles eight separate compiler flags:

Flag What it catches
strictNullChecks Accessing properties on potentially null/undefined values
strictFunctionTypes Contravariant function parameter checking
strictBindCallApply Wrong argument types for bind/call/apply
strictPropertyInitialization Class properties that may be uninitialized
noImplicitAny Variables inferred as any
noImplicitThis this without an explicit type
alwaysStrict Emits "use strict" in output
useUnknownInCatchVariables Catch variables typed as unknown instead of any

The most important of these is strictNullChecks. Without it, null and undefined are assignable to every type. That means TypeScript will not warn you when you do:

function getUser(id: string): User {
  return db.findUser(id); // returns User | null in reality
}

const user = getUser("123");
console.log(user.email); // Runtime crash if not found

With strictNullChecks, this does not compile. You are forced to handle the null case.

The “But It’s Too Noisy” Objection

Teams that turn off strict mode usually do it because they inherited a large JavaScript codebase and enabling strict breaks everything at once. That is understandable for migration. It is not acceptable for new projects.

For migrations, TypeScript gives you an escape hatch: // @ts-ignore and the any type. You can incrementally type a codebase by adding strict to new files only, then gradually fixing the old ones. The checkJs flag and per-file directives let you do this without a big-bang rewrite.

The teams that disable strict “just for now” and never revisit it are the teams shipping null pointer exceptions to production.

The Bugs Strict Mode Prevents in Practice

Here is a real pattern that strict mode catches:

interface Config {
  apiUrl: string;
  timeout: number;
  retries?: number;
}

function buildRequest(config: Config) {
  // Without strict: this compiles fine, crashes if retries is undefined
  const retryCount = config.retries.toString();

  // With strict: error - 'config.retries' is possibly 'undefined'
  // You are forced to write:
  const retryCount = (config.retries ?? 3).toString();
}

Optional properties are a source of runtime bugs in almost every non-strict TypeScript project. Strict mode makes the optional-ness visible at the call site.

noImplicitAny Is Equally Important

Without noImplicitAny, TypeScript silently widens types when it cannot infer them:

function processItems(items) { // 'items' implicitly has 'any' type
  items.forEach(item => {
    item.nonExistentMethod(); // No error, 'any' accepts everything
  });
}

Every implicit any is a hole in your type system. You get the ceremony of TypeScript with none of the safety.

The strictest Config Worth Using

For new projects, start with this tsconfig base:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  }
}

noUncheckedIndexedAccess adds T | undefined to array index access, catching:

const arr = [1, 2, 3];
const first = arr[0]; // type is number | undefined with this flag
const doubled = first * 2; // Error - possibly undefined

This flag is not part of strict yet but it should be. Array index bugs are some of the most common TypeScript runtime errors.

What About Performance?

Strict mode does not affect runtime performance. TypeScript types are erased at compile time. The only cost is slower type-checking for very large codebases, and that is a build tooling problem, not a code quality tradeoff.

If your TypeScript compilation is slow, the answer is to use project references and incremental compilation, not to disable strict checks.

Migrating a Large Codebase

The practical path for adding strict to an existing project:

  1. Add strict: true to tsconfig
  2. Run tsc --noEmit to see the error count
  3. Create a separate tsconfig.strict.json that extends the base and applies to new files only
  4. Fix the highest-impact errors first - usually strictNullChecks violations
  5. Use // @ts-expect-error (not // @ts-ignore) for temporary suppressions - it will warn you when the error is fixed and the comment is no longer needed

A codebase with 500 strict errors is fixable in a few sprints. A codebase with strict: false will keep generating new bugs indefinitely.

Bottom Line

TypeScript without strict mode is a linter with extra syntax. It gives you the cognitive overhead of types with a fraction of the actual safety. Every project that disables it will, eventually, ship the exact class of runtime errors that TypeScript exists to prevent.

Enable strict mode on day one. For existing projects, turn it on now and fix the errors incrementally. The short-term pain is completely worth it.