Strict Mode Deep Dive
No loose clothing, no missing badges — everything must be perfect
"strict": true in tsconfig enables ALL of TypeScript's strict type-checking options at once. It's the Nizam's Strictest Darbar Guard — this guard doesn't let ANYTHING slide. No loose clothing, no missing badges, no "I'll add the type later." Everything must be perfect.
Without strict mode, TypeScript is lenient — it lets you write code that might have hidden bugs. With strict mode, it catches those bugs at compile time.
Think of the Nizam's strictest security checkpoint. The regular guard lets you through with a quick glance — "okay, you look fine." The STRICT guard checks EVERYTHING — "Show me your ID. Where's your badge? Why isn't your shirt tucked in? What's in that bag? No badge, no entry!" It's annoying at first, but it keeps the darbar safe.
"strict": true enables a whole family of checks:
{
"compilerOptions": {
"strict": true
}
}
// Enables ALL of these:
// - strictNullChecks
// - noImplicitAny
// - strictFunctionTypes
// - strictBindCallApply
// - strictPropertyInitialization
// - noImplicitThis
// - alwaysStrict
Each one catches a different category of bugs. Turning them all on at once ensures your codebase is fully protected. Seedha samjho: strict mode = Nizam ka sabse strict guard, bina badge ke andar nahi jaane ka! You might grumble at the checkpoint, but you'll thank the guard when your code runs without crashing in production.
strictNullChecks is the most impactful strict option. Without it, undefined and null are assignable to ANY type. With it, you must EXPLICITLY handle null and undefined.
See the problem without strict:
// strictNullChecks: false
let name: string = null; // No error!
name.toUpperCase(); // Runtime CRASH!
TypeScript silently allows null to be assigned to a string. At runtime, your code crashes with "Cannot read property of null." Now with strict:
// strictNullChecks: true
let name: string = null;
// ERROR! Type 'null' is not
// assignable to type 'string'
You must explicitly declare that null is possible:
let name: string | null = null;
// OK, we acknowledge null
if (name !== null) {
// Now safe to use
name.toUpperCase(); // OK!
}
The DOM example is where this shines most. document.getElementById returns HTMLElement | null because the element might not exist:
const app = document.getElementById(
"app"
);
// Type: HTMLElement | null
// Without strict: no error (risky!)
app.innerHTML = "hello"; // crash risk
// With strict: MUST check first
if (app) {
app.innerHTML = "hello"; // safe!
}
This prevents the #1 JavaScript runtime error: "Cannot read property of null." The same applies to undefined — let arr: number[] = undefined is an error with strict on. Must be number[] | undefined. This single flag eliminates entire categories of runtime crashes from your codebase.
noImplicitAny errors whenever TypeScript can't infer a type and would fall back to any. This stops hidden any from sneaking into your code.
// noImplicitAny: false
function fn(x) {
return x.toUpperCase();
// x is implicitly 'any', no error
}
// noImplicitAny: true
function fn(x) { // ERROR!
// Parameter 'x' implicitly has
// an 'any' type
}
function fn(x: string) { // OK!
return x.toUpperCase();
}
strictFunctionTypes ensures function parameter types are checked contravariantly (correctly). Without it, unsafe function assignments slip through:
// strictFunctionTypes: true
type StringFn = (x: string) => void;
type MixedFn = (
x: string | number
) => void;
const fn: MixedFn = (
x: string
) => {}; // ERROR! Correct!
strictPropertyInitialization ensures class properties are initialized in the constructor:
// strictPropertyInitialization: true
class User {
name: string; // ERROR!
// Property 'name' has no
// initializer and is not
// definitely assigned in
// the constructor.
// Fix 1: Initialize
name: string = "";
// Fix 2: Assert (!)
name!: string; // "Trust me!"
// Fix 3: Constructor
constructor() {
this.name = "guest";
}
}
noImplicitThis errors when this has an implicit any type, common in callbacks and object methods. strictBindCallApply ensures .bind(), .call(), and .apply() are called with correct argument types. Together, these flags form an impenetrable security perimeter around your code.
Strict mode is powerful, but enabling it naively can cause pain. Let's walk through the most common traps.
Trap 1: Enabling strict on an existing project. Turning on strict in a large JS/TS project generates HUNDREDS of errors. Strategy: enable one flag at a time, fix errors, then enable the next.
// tsconfig.json — gradual approach
{
"compilerOptions": {
// Step 1: fix nulls first
"strictNullChecks": true,
// Step 2: fix implicit any
"noImplicitAny": true,
// Step 3: enable full strict
"strict": true
}
}
Trap 2: Using ! to silence strictPropertyInitialization. name!: string tells TS "trust me, this will be set." But if it ISN'T set at runtime, you get undefined behavior. Use ! only when you're certain — frameworks like Angular do this heavily, but they have their own initialization lifecycle.
Trap 3: Forgetting to handle null from DOM APIs.
// document.querySelector returns
// Element | null
const box = document.querySelector(
".box"
);
box.textContent = "hi"; // ERROR!
// Fix: optional chaining
box?.textContent; // safe access
// Or explicit check
if (box) {
box.textContent = "hi"; // OK
}
Trap 4: Thinking strict catches EVERYTHING. Strict mode catches TYPE errors, not LOGIC errors. if (x > 0) when you meant if (x < 0) is a logic error that TS can't catch. The guard checks your badge, not your intentions!
Trap 5: Turning off strict to "fix" errors. Using // @ts-ignore or setting strict: false is a temporary bandage, not a solution. Fix the type errors properly! Every suppression is a potential bug hiding in plain sight.
Time for the rapid-fire cheatsheet. Pin this to your desk or carve it on the darbar wall — whatever works!
What strict: true Enables:
// All these flags at once:
"strictNullChecks": true
// null/undefined not assignable
// to everything
"noImplicitAny": true
// no hidden any types
"strictFunctionTypes": true
// correct function checking
"strictBindCallApply": true
// correct bind/call/apply
"strictPropertyInitialization": true
// class props must be set
"noImplicitThis": true
// no implicit any on this
"alwaysStrict": true
// "use strict" in JS output
Most Impactful Flags:
// 1. strictNullChecks
// Prevents "Cannot read property
// of null" runtime crashes
// 2. noImplicitAny
// Prevents hidden any from
// silently infecting your code
Migration Strategy:
- Always use
strict: truefor NEW projects - Migrate existing projects gradually — one flag at a time
- Start with
strictNullChecks, thennoImplicitAny - Don't use
!or@ts-ignoreas crutches - Strict mode is your friend, not your enemy
The golden rule: "Strict mode is the Nizam's strictest guard — annoying at first, but keeps the darbar safe. Don't bribe the guard with ! or @ts-ignore, bhai. Fix your code properly!"
Key Takeaways
- "strict": true enables all strict type-checking flags at once
- strictNullChecks prevents null/undefined from being assigned to any type
- noImplicitAny forces you to explicitly type parameters when TS can't infer
- strictPropertyInitialization requires class properties to be initialized
- Migrate existing projects gradually — enable one flag at a time
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login