tsconfig Strict Flags
What strict mode actually enables, flag-by-flag impact on frontend code, and a practical migration order for legacy apps.
- typescript
- tsconfig-strict
"strict": true is the baseline for serious TypeScript. It is a bundle of checks. Knowing the pieces helps you migrate legacy apps one lever at a time and explain tradeoffs in reviews.
Docs: TSConfig Reference — strict, Compiler Options.
What strict turns on
As of modern TypeScript, strict includes roughly:
| Flag | Effect |
|---|---|
noImplicitAny |
Error on implied any |
strictNullChecks |
null/undefined not in every type |
strictFunctionTypes |
Stronger function param checking (contravariance) |
strictBindCallApply |
Typed bind/call/apply |
strictPropertyInitialization |
Class props must be initialized |
noImplicitThis |
this must be typed |
alwaysStrict |
Emit/parse in JS strict mode |
useUnknownInCatchVariables |
catch (e) → unknown (in current strict bundles / recommended) |
Exact membership can evolve — check your version’s docs. Enabling strict is still the right default for new apps.
The two that change frontend code most
strictNullChecks
const el = document.getElementById('app');
// el is HTMLElement | null
// el.textContent = 'x'; // error
el?.textContent = 'x'; // wrong assignment form
if (el) el.textContent = 'x';
Optional chaining and nullish coalescing become daily tools.
noImplicitAny
// error under noImplicitAny
function log(x) {
console.log(x);
}
function log(x: unknown) {
console.log(x);
}
Recommended extras (not always in strict)
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"skipLibCheck": true
}
}
| Flag | Why |
|---|---|
noUncheckedIndexedAccess |
arr[i] and obj[key] include undefined |
noImplicitOverride |
Must mark override on subclasses |
exactOptionalPropertyTypes |
Distinguishes missing vs undefined |
skipLibCheck |
Faster builds; skip typechecking .d.ts noise |
noUncheckedIndexedAccess is noisy but catches real bugs. Enable when the team can pay the annotation cost.
Minimal modern app skeleton
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src"]
}
Bundlers own emit; noEmit + isolatedModules matches Vite/Next patterns. See path aliases.
Migrating a loose codebase
- Enable
stricton new packages only (project references). - Or enable flag-by-flag:
noImplicitAny→strictNullChecks→ rest. - Use
// @ts-expect-errorwith tickets — not blanketany. - Don’t disable strict in CI “temporarily” without an owner.
// Prefer
// @ts-expect-error legacy API — JIRA-123
legacyCall();
// Avoid
// @ts-ignore
Footguns
strict: falsewith some flags true — document the matrix.- Different tsconfigs for app vs tests — tests often need DOM + vitest types.
skipLibCheck: falseon huge monorepos — slow CI.- Assuming strict fixes runtime — still validate network data.
Interview out-loud answer
“strict enables a set of soundness checks; null checks and noImplicitAny are the big frontend ones. I start new apps with strict: true, consider noUncheckedIndexedAccess, and migrate legacy code flag-by-flag with ts-expect-error rather than any sprawl.”
CI command
"scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit"
}
Run typecheck in CI on every PR. Locally, editor typechecking is not enough if skipLibCheck or project references hide another package’s errors — typecheck the package graph you ship.
Related on this site
- unknown vs any
- Non-null assertion operator
- Path aliases and tooling sync
- TypeScript for JavaScript engineers
- Basic types and annotations
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.