Overview

TypeScript 6 made strict: true the default, so a new tsconfig.json is strict without saying so. Still write it explicitly: older toolchains, shared base configs, and editors that read the file all behave better when the intent is on the page. strict is a floor; three useful flags sit outside it. Read typescript for the umbrella rules and typescript-tsconfig for the other options.

Never disable an individual strict flag

strict enables alwaysStrict, strictNullChecks, strictBindCallApply, strictBuiltinIteratorReturn, strictFunctionTypes, strictPropertyInitialization, noImplicitAny, noImplicitThis, and useUnknownInCatchVariables. Switching one off to clear errors hides the bugs it exists to find. Adopt strict on day one: retrofitting it later means fixing hundreds of errors a new project never writes.

{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true,
  "exactOptionalPropertyTypes": true, "noImplicitOverride": true } }

Add noUncheckedIndexedAccess

Without it, items[10] is typed string and is undefined at runtime. With it, indexing yields T | undefined, so the compiler demands the check (typescript-narrowing). It is the flag that most often catches a real bug.

const value = items[10]
if (value !== undefined) console.log(value.toUpperCase())

Add exactOptionalPropertyTypes

paidAt?: Date normally accepts both a missing key and an explicit undefined. With the flag on, only a missing key is legal, so you must model undefined as a real value (paidAt?: Date | undefined) when it is one. Pick which meaning you intend; many serializers treat the two differently.

Add noImplicitOverride

A redefined method must say override, so renaming the parent method breaks the subclass at compile time instead of silently shadowing it.

Narrow catch variables

useUnknownInCatchVariables types the caught value as unknown. Check err instanceof Error before reading .message; anything can be thrown, including strings.

Migrate a loose codebase by file, not by flag

  1. Create tsconfig.strict.json that extends the main config, turns the strict flags on, and lists only files that already pass.
  2. Fix a file when you touch it, then add it to the list.
  3. Use // @ts-expect-error with a reason for a temporary hole; it errors once the hole closes. Never @ts-ignore.
  4. When the list covers everything, delete the loose config and set the flags in the main one.

Track progress by counting files in the strict list. Do not ship strict: false backed by hand-written as any casts.

Flag any and casts in review and lint

as any, as unknown as T, and @ts-ignore each undo strict mode locally. Enforce with typescript-eslint rules no-explicit-any, the no-unsafe-* family, and ban-ts-comment (require a description). The fix for most casts is a schema parse at the boundary; see typescript-runtime-validation.