Kodokon kodokon.com

Strict tsconfig: key options and common errors

You understand what strict mode actually enables and know how to handle the typical errors it reveals in an existing project.

11 min · 3 questions

Open this lesson in Kodokon

"strict": true is not one option but an aggregate that enables a whole family of checks: strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, and alwaysStrict. Each new TypeScript version may add more: enabling strict subscribes you to future checks. Every flag can still be disabled individually, which allows a gradual migration on an existing codebase.

JSON
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noFallthroughCasesInSwitch": true,
    "exactOptionalPropertyTypes": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true
  }
}
A strict baseline extended with options outside the aggregate (JSON file).

The two highest-impact flags are strictNullChecks and noImplicitAny. The first removes null and undefined from all types: every potentially absent value must be declared T | undefined and handled before use. The second refuses to let an unannotated parameter fall back to any. Together, they eliminate the two big families of JavaScript bugs: "cannot read property of undefined" and the propagation of unchecked values. useUnknownInCatchVariables completes the picture: the variable in a catch is unknown, because JavaScript lets you throw anything.

TYPESCRIPT
function findPort(
  env: Record<string, string | undefined>
): number {
  const raw = env["PORT"];
  if (raw === undefined) {
    return 3000;
  }
  return Number.parseInt(raw, 10);
}

declare function riskyOperation(): void;

try {
  riskyOperation();
} catch (err) {
  const message =
    err instanceof Error ? err.message : String(err);
  console.error(message);
}
Two strict patterns: absence handled, catch as unknown.

Common migration errors, and their real remedies. "Object is possibly undefined": prefer narrowing (if), optional chaining ?., or a ?? default over the non-null assertion !, which merely moves the crash elsewhere. "Property has no initializer" (strictPropertyInitialization): initialize in the constructor rather than reaching for ! on the field. Also watch out for the ?? versus || trap: port || 3000 also replaces 0 and "", whereas port ?? 3000 only replaces null and undefined.

TYPESCRIPT
interface Settings { retries?: number }

function resolveRetries(s: Settings): number {
  const bad = s.retries || 3;
  const good = s.retries ?? 3;
  return good;
}

const names = ["Ada", "Linus"];
const first = names[0];
const upper = first?.toUpperCase() ?? "N/A";
retries: 0 gives bad = 3 but good = 0; the index access is checked next.

Beyond the aggregate, three options deserve a team discussion. noUncheckedIndexedAccess adds undefined to every indexed access (arr[i], dict[key]): safer, but verbose in array-manipulation code - many teams adopt it on new projects only. exactOptionalPropertyTypes distinguishes "property absent" from "property set to undefined", crucial for patches. skipLibCheck skips checking the .d.ts files of your dependencies: a deliberate trade-off between build time and detecting conflicts between libraries.

Knowledge check

Make sure you remember the key points of this lesson.

  1. What exactly does "strict": true do in tsconfig.json?
    • It enables only the strictNullChecks option
    • It enables a set of strict flags, each of which can still be disabled individually
    • It forbids any use of the any type, even explicit
    • It forces compilation in ES5 mode
  2. With retries?: number set to 0, what is the difference between retries || 3 and retries ?? 3?
    • None, both return 0
    • || returns 3 because 0 is falsy; ?? returns 0 because it only replaces null and undefined
    • ?? returns 3 because 0 is treated as absent
  3. Why prefer // @ts-expect-error over // @ts-ignore to mark typing debt?
    • @ts-expect-error suppresses several errors at once
    • @ts-ignore does not work in strict mode
    • @ts-expect-error becomes an error once the problem is fixed, so the debt cleans itself up