The difference between leaky and trustworthy TypeScript is mostly configuration; these flags define what the checker catches.
"strict": true is a bundle of options, and new strict checks added in later versions join it automatically. The main ones:
noImplicitAny— unannotated parameters can't silently becomeany.strictNullChecks—null/undefinedare separate types.strictFunctionTypes— function parameters are checked contravariantly (see the variance lesson).strictPropertyInitialization— class fields must be initialised.strictBindCallApply—call,applyandbindare checked against the function's signature.useUnknownInCatchVariables—catch (e)givese: unknown.noImplicitThisandalwaysStrict.
You can switch individual members off ("strict": true, "strictPropertyInitialization": false) while migrating.
Valuable checks outside strict:
noUncheckedIndexedAccess—arr[i]andrecord[key]includeundefined. Catches real out-of-range bugs; costs some!or checks in loops (preferfor...of).exactOptionalPropertyTypes—p?: stringno longer accepts an explicitundefined.noImplicitOverride— requireoverrideon overriding methods.noImplicitReturns,noFallthroughCasesInSwitch— control-flow sanity.noPropertyAccessFromIndexSignature— forceobj["key"]for dynamic keys.
Project hygiene flags: isolatedModules (each file can be compiled alone, required by esbuild/swc/Babel), verbatimModuleSyntax, and skipLibCheck.
To migrate an existing codebase, enable strict, then suppress the remaining errors per line with // @ts-expect-error (which itself errors once the problem is fixed, unlike // @ts-ignore) and pay them down gradually. Tools like typescript-strict-plugin can enforce strictness file by file.
Example
// Compiled with: "strict": true, "noUncheckedIndexedAccess": true
const names = ["Ada", "Grace"];
const third = names[2]; // string | undefined
// @ts-expect-error -- 'third' is possibly 'undefined'
console.log(third.toUpperCase());
for (const n of names) console.log(n.toUpperCase()); // for...of: n is string
const ports: Record<string, number> = { http: 80 };
const https = ports["https"]; // number | undefined
console.log(https ?? 443);
function greet(this: { name: string }, greeting: string) {
return `${greeting}, ${this.name}`;
}
// strictBindCallApply checks arguments:
// @ts-expect-error -- Argument of type 'number' is not assignable to 'string'
greet.call({ name: "Ada" }, 42);
console.log(greet.call({ name: "Ada" }, "Hi"));Remember
- Always start with
strict: true - Add
noUncheckedIndexedAccessfor honest index reads - Prefer
@ts-expect-errorover@ts-ignorefor temporary suppressions - Use
isolatedModuleswhen another tool compiles your files
Watch out for
- Disabling
strictentirely to make a migration compile instead of disabling single sub-flags.