Overview
TypeScript 6 made strict: true the default, so a new tsconfig.json is strict without saying so. Still write it explicitly: older toolchains, shared base configs, and editors that read the file all behave better when the intent is on the page. strict is a floor; three useful flags sit outside it. Read typescript for the umbrella rules and typescript-tsconfig for the other options.
Never disable an individual strict flag
strict enables alwaysStrict, strictNullChecks, strictBindCallApply, strictBuiltinIteratorReturn, strictFunctionTypes, strictPropertyInitialization, noImplicitAny, noImplicitThis, and useUnknownInCatchVariables. Switching one off to clear errors hides the bugs it exists to find. Adopt strict on day one: retrofitting it later means fixing hundreds of errors a new project never writes.
{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true, "noImplicitOverride": true } }Add noUncheckedIndexedAccess
Without it, items[10] is typed string and is undefined at runtime. With it, indexing yields T | undefined, so the compiler demands the check (typescript-narrowing). It is the flag that most often catches a real bug.
const value = items[10]
if (value !== undefined) console.log(value.toUpperCase())Add exactOptionalPropertyTypes
paidAt?: Date normally accepts both a missing key and an explicit undefined. With the flag on, only a missing key is legal, so you must model undefined as a real value (paidAt?: Date | undefined) when it is one. Pick which meaning you intend; many serializers treat the two differently.
Add noImplicitOverride
A redefined method must say override, so renaming the parent method breaks the subclass at compile time instead of silently shadowing it.
Narrow catch variables
useUnknownInCatchVariables types the caught value as unknown. Check err instanceof Error before reading .message; anything can be thrown, including strings.
Migrate a loose codebase by file, not by flag
- Create
tsconfig.strict.jsonthatextendsthe main config, turns the strict flags on, and lists only files that already pass. - Fix a file when you touch it, then add it to the list.
- Use
// @ts-expect-errorwith a reason for a temporary hole; it errors once the hole closes. Never@ts-ignore. - When the list covers everything, delete the loose config and set the flags in the main one.
Track progress by counting files in the strict list. Do not ship strict: false backed by hand-written as any casts.
Flag any and casts in review and lint
as any, as unknown as T, and @ts-ignore each undo strict mode locally. Enforce with typescript-eslint rules no-explicit-any, the no-unsafe-* family, and ban-ts-comment (require a description). The fix for most casts is a schema parse at the boundary; see typescript-runtime-validation.