Skip to content
TypeScript ts best-practices 4 min read

Always Use Strict Mode

Strict mode is the most important decision you will make in a TypeScript project, and the right answer is almost always to turn it on. The single strict: true flag in tsconfig.json enables a family of checks that catch the bugs TypeScript is best at preventing — null dereferences, untyped values, and unsound this usage. Without it, large portions of your code silently fall back to any, and the compiler’s guarantees quietly evaporate. This page explains exactly what strict enables, why each check matters, and how to adopt it in an existing codebase.

Turning strict on

Enable every strict-family check with one line. The tsc --init template has shipped with strict: true enabled by default since TypeScript 4.x, so new projects already start here.

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "NodeNext"
  }
}

strict is an umbrella flag: setting it to true flips on every member of the strict family at once. You can still override an individual member underneath it (for example "strictPropertyInitialization": false), but treat that as a temporary, documented exception rather than a habit.

What strict enables

The umbrella expands to a fixed set of flags. Knowing them individually helps you understand error messages and reason about migrations.

FlagWhat it does
noImplicitAnyErrors on values whose type can’t be inferred and would default to any
strictNullChecksMakes null and undefined distinct types you must handle
strictFunctionTypesChecks function parameter types contravariantly (safer assignments)
strictBindCallApplyType-checks bind, call, and apply arguments
strictPropertyInitializationRequires class fields to be assigned in the constructor
useUnknownInCatchVariablesTypes catch clause variables as unknown instead of any
alwaysStrictParses files in ECMAScript strict mode and emits "use strict"
noImplicitThisErrors when this has an implied any type

Every one of these closes a hole through which untyped or unsound values would otherwise leak.

strictNullChecks: the big one

The highest-impact member is strictNullChecks. Without it, null and undefined are assignable to every type, so the compiler cannot warn you about the most common runtime crash in JavaScript.

function getLength(text: string | null) {
  // ❌ Error: 'text' is possibly 'null'.
  return text.length;
}

function safeLength(text: string | null) {
  if (text === null) return 0;
  return text.length; // narrowed to 'string' here
}

With the flag on, string | null is honestly different from string, and TypeScript forces you to narrow before accessing members. The bug that would have thrown Cannot read properties of null is now a compile error.

noImplicitAny: no silent escapes

noImplicitAny reports any value that would otherwise silently become any. This is what keeps your type coverage from rotting over time.

// ❌ Error: Parameter 'item' implicitly has an 'any' type.
function format(item) {
  return item.toUpperCase();
}

// ✅ Annotate it
function formatStrict(item: string) {
  return item.toUpperCase();
}

Implicit any is contagious: one untyped parameter spreads through every expression that touches it. Forcing an explicit type (or letting inference do its job from a typed source) keeps the contagion contained.

Strict mode does not change emitted JavaScript — it only changes which programs the compiler accepts. Turning it on can never break your runtime behavior, only surface problems that were already there.

Adopting strict gradually

Flipping strict on in a mature codebase can produce hundreds of errors. Migrate incrementally by enabling members one at a time, hardest-hitting last.

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true
    // enable the rest, then finally collapse to "strict": true
  }
}

Start with noImplicitAny and strictNullChecks, fix the fallout per directory, then add the remaining flags. Once the codebase is clean, replace the individual flags with "strict": true so future additions are covered automatically. Tools like ts-strictify or a CI check that forbids new errors keep you from backsliding during the transition.

Best Practices

  • Set "strict": true in every new project and treat it as non-negotiable.
  • Prefer the umbrella flag over enabling members individually once your code is clean.
  • Migrate legacy code gradually: noImplicitAny and strictNullChecks first, then the rest.
  • Never disable a strict member globally to silence a single error — fix the code or narrow locally.
  • Combine strict with noUncheckedIndexedAccess for even stronger array/record safety.
  • Add a CI gate so the strict error count can only go down, never up.
  • Document any per-flag override with a comment explaining why it is temporary.
Last updated June 29, 2026
Was this helpful?