You understand what strict mode actually enables and know how to handle the typical errors it reveals in an existing project.
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.
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
}
}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.
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);
}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.
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";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.
"strict": true do in tsconfig.json?retries?: number set to 0, what is the difference between retries || 3 and retries ?? 3?// @ts-expect-error over // @ts-ignore to mark typing debt?