TypeScript Strict Mode — Enable and Fix Errors
Advertisement
Strict mode is TypeScript's safety harness. It enforces strict null checks, implicit any errors, and function type compatibility. Enabling it transforms TypeScript from a helpful suggestion system into a powerful type-safety guarantee.
Enabling Strict Mode
{
"compilerOptions": {
"strict": true
}
}
This single setting enables eight strictness flags. You can also configure them individually:
{
"compilerOptions": {
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
noImplicitAny
The most important strict flag. It prevents variables from having an implicit any type.
// ❌ Error with strict mode
function greet(name) {
return `Hello, ${name}!`;
}
// ✅ Fixed
function greet(name: string): string {
return `Hello, ${name}!`;
}
// ❌ Error
const multiply = (a, b) => a * b;
// ✅ Fixed
const multiply = (a: number, b: number): number => a * b;
- Enabling Strict Mode
- noImplicitAny
- strictNullChecks
- noImplicitThis
- strictFunctionTypes
- strictBindCallApply
- strictPropertyInitialization
- noImplicitReturns
- noFallthroughCasesInSwitch
- Migration Strategy
- Phase 1: Enable Strict Mode
- Phase 2: Fix Errors
- Phase 3: Build Type-Safe Practices
- Common Patterns for Strict Mode
- Handling Null/Undefined
- Function Overloads
- Performance Impact
- FAQ
strictNullChecks
Prevents null and undefined from being assignable to any type except their own types.
// ❌ Error with strict mode
function printLength(str: string) {
console.log(str.length);
}
printLength(null); // Error: null is not assignable to string
// ✅ Fixed - explicitly allow null
function printLength(str: string | null) {
if (str === null) return;
console.log(str.length);
}
// ✅ Or use non-null assertion (use sparingly)
printLength(maybeString!);
noImplicitThis
Prevents this from having an implicit any type.
// ❌ Error with strict mode
function processData() {
console.log(this.value);
}
// ✅ Fixed - specify this type
function processData(this: { value: string }) {
console.log(this.value);
}
// ✅ Or use arrow functions in classes
class DataProcessor {
value = "data";
process = () => {
console.log(this.value); // this is bound
};
}
strictFunctionTypes
Enforces stricter function type checking, especially for parameters.
// ❌ Error with strict mode
function printString(x: string) {
console.log(x);
}
let printSomething: (x: string | number) => void = printString;
// Error: function with string param cannot be assigned to function with string | number param
// ✅ Fixed - contravariance
let printSomething: (x: string) => void = printString;
// ✅ Or use more general parameter type
function print(x: string | number) {
console.log(x);
}
let printSomething: (x: string | number) => void = print;
strictBindCallApply
Enables strict type checking for call(), apply(), and bind().
// ❌ Error with strict mode
function greet(greeting: string, name: string) {
console.log(greeting + " " + name);
}
const boundGreet = greet.bind(null);
boundGreet("Hello"); // Error: missing 'name' argument
// ✅ Fixed
const boundGreet = greet.bind(null, "Hello");
boundGreet("Alice"); // OK
// ✅ Or provide correct arguments
greet.call(null, "Hello", "Alice");
strictPropertyInitialization
Requires all class properties to be initialized or declared optional.
// ❌ Error with strict mode
class User {
name: string; // Error: not initialized
email: string; // Error: not initialized
}
// ✅ Fixed - initialize in constructor
class User {
name: string;
email: string;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
// ✅ Or make optional
class User {
name?: string;
email?: string;
}
// ✅ Or use definite assignment assertion (use sparingly)
class User {
name!: string;
email!: string;
initialize(name: string, email: string) {
this.name = name;
this.email = email;
}
}
noImplicitReturns
Ensures all code paths return a value.
// ❌ Error with strict mode
function getValue(type: "string" | "number"): string | number {
if (type === "string") {
return "hello";
} else if (type === "number") {
return 42;
}
// Error: not all code paths return
}
// ✅ Fixed
function getValue(type: "string" | "number"): string | number {
if (type === "string") {
return "hello";
}
return 42;
}
// ✅ Or use switch with exhaustive checking
function getValue(type: "string" | "number"): string | number {
switch (type) {
case "string":
return "hello";
case "number":
return 42;
default:
const exhaustive: never = type;
return exhaustive;
}
}
noFallthroughCasesInSwitch
Prevents switch cases from falling through without a break.
// ❌ Error with strict mode
switch (value) {
case "a":
console.log("A");
case "b":
console.log("B"); // Error: fallthrough
break;
}
// ✅ Fixed
switch (value) {
case "a":
console.log("A");
break;
case "b":
console.log("B");
break;
}
// ✅ Or explicitly allow fallthrough
switch (value) {
case "a":
case "b":
console.log("A or B");
break;
}
Migration Strategy
Phase 1: Enable Strict Mode
{
"compilerOptions": {
"strict": true
}
}
Phase 2: Fix Errors
Start with the most impactful:
noImplicitAny- add type annotationsstrictNullChecks- add null checksstrictFunctionTypes- fix function signatures- Others as they appear
Phase 3: Build Type-Safe Practices
// Create a types.ts for shared interfaces
// src/types/index.ts
export interface User {
id: number;
name: string;
email: string;
}
export interface ApiResponse<T> {
status: "success" | "error";
data: T;
error?: string;
}
// Use throughout project
import type { User, ApiResponse } from "./types";
export async function getUser(id: number): Promise<ApiResponse<User>> {
// Implementation
return { status: "success", data: { id: 1, name: "Alice", email: "alice@example.com" } };
}
Common Patterns for Strict Mode
Handling Null/Undefined
// Type guard
function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
const values: (string | null)[] = ["a", null, "b"];
const nonNull = values.filter(isNotNull); // string[]
// Nullish coalescing
const name: string = userInput ?? "Guest";
// Optional chaining
const email: string | undefined = user?.email;
// Logical OR for truthy
const port: number = parseInt(process.env.PORT || "3000", 10);
Function Overloads
// Multiple signatures for strict type safety
function process(value: string): string;
function process(value: number): number;
function process(value: string | number): string | number {
if (typeof value === "string") {
return value.toUpperCase();
}
return value * 2;
}
const str = process("hello"); // string
const num = process(42); // number
Performance Impact
Strict mode has no runtime performance impact. It's a compile-time safety feature. The only cost is development time to add types.
FAQ
Q: Can I enable strict mode gradually? A: Yes, enable individual flags or use skipLibCheck: true to ignore external library errors while you migrate your own code.
Q: What about legacy code that's hard to type? A: Use any for specific lines with a comment. Better: refactor gradually. Bad types are worse than no types.
Q: Does strict mode catch all runtime errors? A: No. TypeScript is not a runtime type checker. It catches many common errors but can't guarantee runtime safety. Use runtime validation for untrusted data.
Enabling strict mode is the best investment you can make in code quality. Yes, you'll see errors initially. But those errors represent bugs waiting to happen. Fix them now, not in production. Your future self will thank you.
Advertisement