What strict: true Actually Enables
Most TypeScript tutorials say "enable strict mode." Few explain what it turns on. strict: true in tsconfig.json is a shorthand for enabling 7 separate compiler flags:
{
"compilerOptions": {
"strict": true
// Equivalent to:
// "noImplicitAny": true,
// "strictNullChecks": true,
// "strictFunctionTypes": true,
// "strictBindCallApply": true,
// "strictPropertyInitialization": true,
// "noImplicitThis": true,
// "useUnknownInCatchVariables": true
}
}
Each one catches a different class of bug. Here's what they do.
1. noImplicitAny โ No Silent any
Without it, TypeScript infers any when it can't determine a type, silently opting out of type checking.
// Without noImplicitAny โ no error, but `data` is `any`
function process(data) {
return data.length // Could crash at runtime
}
// With noImplicitAny โ error: Parameter 'data' implicitly has an 'any' type
// Forces you to be explicit:
function process(data: string | unknown[]) {
return data.length
}
This is the single most important check. Implicit any is how TypeScript becomes JavaScript with extra steps.
2. strictNullChecks โ null Is Not a Value
Without this, null and undefined are assignable to any type. This is the source of most TypeScript runtime crashes.
// Without strictNullChecks โ valid code, crashes at runtime
const user: User = getUser() // might return null
console.log(user.name) // TypeError: Cannot read properties of null
// With strictNullChecks โ caught at compile time
const user: User | null = getUser()
if (user) {
console.log(user.name) // Safe
}
3. strictFunctionTypes โ Contravariant Parameter Checking
This prevents subtle bugs in function parameter handling:
type Handler = (event: MouseEvent) => void
type SpecificHandler = (event: ClickEvent) => void // ClickEvent extends MouseEvent
// Without strictFunctionTypes โ allowed, but wrong
const handler: Handler = (e: ClickEvent) => e.clientX
// With strictFunctionTypes โ error
// You can't substitute a handler that only works with ClickEvent
// where a handler for any MouseEvent is expected
4. strictBindCallApply โ Typed bind, call, apply
function greet(name: string, age: number): string {
return `${name} is ${age}`
}
// Without strict โ no error
greet.call(null, 'Alice', 'not a number') // Wrong type, no error
// With strictBindCallApply โ error: Argument of type 'string' is not assignable to 'number'
5. strictPropertyInitialization โ Class Fields Must Be Initialized
class Database {
connection: Connection // Error: property not initialized in constructor
// Must either:
connection!: Connection // definite assignment assertion (you guarantee it)
// or
connection: Connection | undefined // explicit about the possibility
// or initialize in constructor
constructor() {
this.connection = createConnection()
}
}
6. noImplicitThis โ No Untyped this
class Timer {
delay = 1000
start() {
// Without noImplicitThis โ 'this' is any, crash at runtime
setTimeout(function() {
console.log(this.delay) // undefined โ wrong 'this'
}, 100)
// With noImplicitThis โ error caught, use arrow function instead
setTimeout(() => {
console.log(this.delay) // Correct
}, 100)
}
}
7. useUnknownInCatchVariables โ Caught Errors Are unknown
try {
await fetchData()
} catch (error) {
// Without this flag: error is 'any' โ you can access any property
console.log(error.message) // Might not exist
// With this flag: error is 'unknown' โ must narrow first
if (error instanceof Error) {
console.log(error.message) // Safe
}
}
Migrating an Existing Codebase
Enabling strict mode on an existing codebase can surface hundreds of errors. The pragmatic approach:
{
"compilerOptions": {
"strict": false,
// Enable flags one at a time
"noImplicitAny": true,
"strictNullChecks": true
// Add more as you fix errors
}
}
Start with noImplicitAny and strictNullChecks โ they catch the most bugs. Add the rest incrementally.
Key Takeaways
strict: trueenables 7 separate checks โ not just "be stricter"strictNullChecksandnoImplicitAnycatch the most real-world bugs- Migrate incrementally: enable flags one at a time on existing projects
- New projects: always start with
strict: trueโ retroactive migration is painful