Skip to content

tsconfig, Deep

tsconfig.json has dozens of options, but a handful decide whether TypeScript is actually protecting you. The single most important one is strict. It is not one check — it is a bundle that turns on a family of flags at once, and every one of them catches real bugs.

{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"verbatimModuleSyntax": true
}
}

Turn strict on from day one. Adopting it later, on a large codebase, means fixing hundreds of errors at once — far harder than never letting them accumulate.

strict: true is shorthand for this whole family:

Flag (inside strict)What it catches
strictNullChecksnull and undefined are no longer silently in every type — the biggest win
noImplicitAnyA value that would fall back to any becomes an error you must resolve
strictFunctionTypesFunction parameters are checked contravariantly — unsafe callbacks are caught
strictBindCallApplybind, call, and apply are type-checked against the function signature
strictPropertyInitializationClass fields must be assigned in the constructor (or marked optional)
useUnknownInCatchVariablescatch (e) gives you unknown, not any, forcing you to narrow
alwaysStrictEmits "use strict" and parses in strict mode

The headline is strictNullChecks. Without it, string secretly includes null and undefined, and the compiler cannot warn you about the single most common runtime crash in JavaScript. With it, null and undefined are their own types that you must handle deliberately.

Three options describe the world your code runs in:

  • target — which JavaScript version tsc emits. ES2022 is a safe modern default; a lower target down-levels newer syntax into older equivalents.
  • module — the module format of the output (NodeNext, ESNext, CommonJS, …). This should match how your code is actually loaded.
  • lib — which built-in type declarations are available (for example, DOM for browser globals, ES2022 for newer runtime methods). If you use structuredClone or Array.prototype.at, your lib must include a version that declares them.

strict is the floor, not the ceiling. Two more flags catch bugs that strict alone misses:

// noUncheckedIndexedAccess: true
const arr = [1, 2, 3];
const x = arr[10]; // type is now `number | undefined`, not `number`
x.toFixed(); // ❌ Object is possibly 'undefined' — exactly the bug you want caught
  • noUncheckedIndexedAccess — indexing an array or record can return undefined (index 10 of a 3-element array does), so the type reflects that. Enormously effective against out-of-bounds bugs.
  • exactOptionalPropertyTypes — distinguishes a property set to undefined from a property that is absent, so an optional name?: string no longer silently accepts name: undefined.
What is `strict: true`?
What does `strictNullChecks` change?
With `noUncheckedIndexedAccess`, what is the type of `arr[i]` for `const arr: number[]`?
Why should you enable `strict` at the start of a project rather than later?