You can write TypeScript that compiles cleanly and still crashes at runtime with “Cannot read properties of null.” If that happens, you probably have strict: false in your tsconfig, or you never set it at all.
Strict mode is not a preference. It is the difference between TypeScript actually working and TypeScript being a syntax game.
What Strict Mode Actually Enables
strict: true is a shorthand that toggles eight separate compiler flags:
| Flag | What it catches |
|---|---|
strictNullChecks |
Accessing properties on potentially null/undefined values |
strictFunctionTypes |
Contravariant function parameter checking |
strictBindCallApply |
Wrong argument types for bind/call/apply |
strictPropertyInitialization |
Class properties that may be uninitialized |
noImplicitAny |
Variables inferred as any |
noImplicitThis |
this without an explicit type |
alwaysStrict |
Emits "use strict" in output |
useUnknownInCatchVariables |
Catch variables typed as unknown instead of any |
The most important of these is strictNullChecks. Without it, null and undefined are assignable to every type. That means TypeScript will not warn you when you do:
function getUser(id: string): User {
return db.findUser(id); // returns User | null in reality
}
const user = getUser("123");
console.log(user.email); // Runtime crash if not found
With strictNullChecks, this does not compile. You are forced to handle the null case.
The “But It’s Too Noisy” Objection
Teams that turn off strict mode usually do it because they inherited a large JavaScript codebase and enabling strict breaks everything at once. That is understandable for migration. It is not acceptable for new projects.
For migrations, TypeScript gives you an escape hatch: // @ts-ignore and the any type. You can incrementally type a codebase by adding strict to new files only, then gradually fixing the old ones. The checkJs flag and per-file directives let you do this without a big-bang rewrite.
The teams that disable strict “just for now” and never revisit it are the teams shipping null pointer exceptions to production.
The Bugs Strict Mode Prevents in Practice
Here is a real pattern that strict mode catches:
interface Config {
apiUrl: string;
timeout: number;
retries?: number;
}
function buildRequest(config: Config) {
// Without strict: this compiles fine, crashes if retries is undefined
const retryCount = config.retries.toString();
// With strict: error - 'config.retries' is possibly 'undefined'
// You are forced to write:
const retryCount = (config.retries ?? 3).toString();
}
Optional properties are a source of runtime bugs in almost every non-strict TypeScript project. Strict mode makes the optional-ness visible at the call site.
noImplicitAny Is Equally Important
Without noImplicitAny, TypeScript silently widens types when it cannot infer them:
function processItems(items) { // 'items' implicitly has 'any' type
items.forEach(item => {
item.nonExistentMethod(); // No error, 'any' accepts everything
});
}
Every implicit any is a hole in your type system. You get the ceremony of TypeScript with none of the safety.
The strictest Config Worth Using
For new projects, start with this tsconfig base:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
noUncheckedIndexedAccess adds T | undefined to array index access, catching:
const arr = [1, 2, 3];
const first = arr[0]; // type is number | undefined with this flag
const doubled = first * 2; // Error - possibly undefined
This flag is not part of strict yet but it should be. Array index bugs are some of the most common TypeScript runtime errors.
What About Performance?
Strict mode does not affect runtime performance. TypeScript types are erased at compile time. The only cost is slower type-checking for very large codebases, and that is a build tooling problem, not a code quality tradeoff.
If your TypeScript compilation is slow, the answer is to use project references and incremental compilation, not to disable strict checks.
Migrating a Large Codebase
The practical path for adding strict to an existing project:
- Add
strict: trueto tsconfig - Run
tsc --noEmitto see the error count - Create a separate
tsconfig.strict.jsonthat extends the base and applies to new files only - Fix the highest-impact errors first - usually
strictNullChecksviolations - Use
// @ts-expect-error(not// @ts-ignore) for temporary suppressions - it will warn you when the error is fixed and the comment is no longer needed
A codebase with 500 strict errors is fixable in a few sprints. A codebase with strict: false will keep generating new bugs indefinitely.
Bottom Line
TypeScript without strict mode is a linter with extra syntax. It gives you the cognitive overhead of types with a fraction of the actual safety. Every project that disables it will, eventually, ship the exact class of runtime errors that TypeScript exists to prevent.
Enable strict mode on day one. For existing projects, turn it on now and fix the errors incrementally. The short-term pain is completely worth it.
Comments