TypeScript path · In a real project · Topic 8 of 11

Strictness flags, and why strict matters

Not started

How much TypeScript protects you depends on its configuration. With the defaults of a bare tsc, null and undefined can be assigned to any type and a parameter without a type silently becomes any, so a lot of broken code type-checks. "strict": true in tsconfig.json (which tsc --init writes for you) switches on a family of checks that close those holes. Treat it as the starting point for any new project, not as an advanced setting.

Why it matters: Without `strict`, `null` and `undefined` fit into every type and untyped parameters silently become `any`. That is the majority of the bugs TypeScript is meant to catch.

  1. 1strict is a family

    `"strict": true` turns on a set of checks, including `strictNullChecks` and `noImplicitAny`. New releases can add members to the family.

  2. 2strictNullChecks matters most

    With it, `null` and `undefined` are their own types. A value that may be missing says so in its type, and you must handle it before use.

  3. 3Some useful checks are outside strict

    `noUncheckedIndexedAccess` makes `arr[i]` and `record[key]` include `undefined`; `exactOptionalPropertyTypes` separates a missing property from one set to `undefined`.

  4. 4Adopt it gradually

    In an existing codebase, enable the flags one at a time and fix each kind of error, rather than switching on `strict` and facing thousands at once.

1

How it works

The explanation, with examples you can run.

What is in the family

In TypeScript 5.x, strict enables:

FlagWhat it catches
strictNullChecksUsing a value that may be null or undefined without checking
noImplicitAnyParameters and variables the checker would have to give any
strictFunctionTypesCallbacks that expect a narrower argument than they will be given
strictBindCallApplyWrong arguments to fn.call, fn.apply and fn.bind
strictPropertyInitializationClass fields that the constructor never sets
noImplicitThisthis with an unknown type inside functions
useUnknownInCatchVariablesTreating a caught value as an Error without checking
strictBuiltinIteratorReturnTreating next().value of a finished built-in iterator as a real value (it is undefined, not any)
alwaysStrictEmits "use strict" and parses every file in strict mode

Each can still be turned off individually ("strictPropertyInitialization": false), which is useful while migrating.

The one that matters most

type User = { id: string; name: string }declare const users: User[] const u = users.find((x) => x.id === "u1")console.log(u.name) // Error: 'u' is possibly 'undefined' if (u) console.log(u.name) // fine: narrowed to Userconsole.log(u?.name ?? "unknown") // fine: handles the missing case

Without strictNullChecks, the type of u is plain User, and the first console.log compiles and then throws TypeError: Cannot read properties of undefined the first time no user matches. Missing values are the most common runtime error in JavaScript, and this flag is what lets the checker see them.

noImplicitAny

function total(items) { // Error: Parameter 'items' implicitly has an 'any' type  let sum = 0  for (const item of items) sum += item.price  return sum}

Without the flag, items is silently any and nothing in the function is checked. With it, you have to say what items is, or write any explicitly, where reviewers can see it.

Beyond strict

const scores: Record<string, number> = { ada: 3 }const s = scores["lin"] // number, under strict alone

The lookup returns undefined at runtime, but its type says number. noUncheckedIndexedAccess makes it number | undefined, at the cost of extra checks on array access in loops. exactOptionalPropertyTypes stops { name?: string } from accepting an explicit name: undefined. Neither is in strict; both are worth trying on new code.

Turning it on in an existing project

Switching on strict in a large JavaScript-flavoured codebase can produce thousands of errors. Enable the flags one at a time, starting with noImplicitAny and then strictNullChecks, and fix each kind of error before the next. Tools that track and suppress existing errors per file let new code meet the strict rules while old code catches up.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.With `strictNullChecks` off, what is the type of `u` in `const u = users.find((x) => x.id === id)`?
  2. 2.Which of these is NOT turned on by `"strict": true` in TypeScript 5.x?
  3. 3.Why can upgrading TypeScript produce new errors in a project that uses `strict`?

0 of 3 right so far. Get all three to finish this topic.