TypeScript Null & Undefined
Handling null and undefined values safely
🛡️ Null & Undefined in TypeScript
TypeScript provides strict null checking to prevent common runtime errors. With strictNullChecks enabled, null and undefined are distinct types that must be explicitly handled, making your code safer and more predictable by catching potential null reference errors.
// Strict null checking
let name: string = "Alice";
// name = null; // Error!
let optionalName: string | null = null; // OK
Understanding Null and Undefined
Null
Intentional absence of value
let user: User | null = null;
Undefined
Variable not yet assigned
let age: number | undefined;
Strict Checks
Enable strictNullChecks
"strictNullChecks": true
Type Guards
Check before using values
if (value !== null) {
// Safe to use
}
🔹 Null vs Undefined
Understanding the difference:
// Undefined - variable declared but not assigned
let username: string | undefined;
console.log(username); // undefined
// Null - intentionally empty value
let user: User | null = null;
console.log(user); // null
// Both can be used together
let data: string | null | undefined;
data = "Hello";
data = null;
data = undefined;
🔹 Strict Null Checks
Enable strict null checking in tsconfig.json:
{
"compilerOptions": {
"strictNullChecks": true
}
}
🔸 Without Strict Null Checks
// strictNullChecks: false
let name: string = "Alice";
name = null; // No error
name = undefined; // No error
function greet(name: string) {
console.log(name.toUpperCase());
}
greet(null); // Runtime error!
🔸 With Strict Null Checks
// strictNullChecks: true
let name: string = "Alice";
// name = null; // Error!
// name = undefined; // Error!
// Must explicitly allow null/undefined
let optionalName: string | null = null; // OK
function greet(name: string | null) {
if (name !== null) {
console.log(name.toUpperCase()); // Safe
}
}
🔹 Optional Properties
Use ? to make properties optional:
interface User {
id: number;
name: string;
email?: string; // Optional: string | undefined
phone?: string | null; // Optional and nullable
}
const user1: User = {
id: 1,
name: "Alice"
// email is undefined (not provided)
};
const user2: User = {
id: 2,
name: "Bob",
email: "[email protected]"
};
const user3: User = {
id: 3,
name: "Charlie",
email: undefined,
phone: null
};
🔹 Type Guards for Null Checks
Safely check for null and undefined:
🔸 If Statement
function printName(name: string | null | undefined) {
if (name) {
console.log(name.toUpperCase());
} else {
console.log("No name provided");
}
}
printName("Alice"); // ALICE
printName(null); // No name provided
printName(undefined); // No name provided
🔸 Strict Equality
function processValue(value: string | null | undefined) {
if (value !== null && value !== undefined) {
console.log(`Value: ${value}`);
}
// Or check for null specifically
if (value === null) {
console.log("Value is null");
}
// Or check for undefined specifically
if (value === undefined) {
console.log("Value is undefined");
}
}
🔹 Non-null Assertion Operator (!)
Tell TypeScript a value is not null/undefined:
function getUser(): User | null {
return { id: 1, name: "Alice" };
}
const user = getUser();
// Without assertion - Error
// console.log(user.name); // Error: Object is possibly 'null'
// With assertion - No error (use carefully!)
console.log(user!.name); // OK, but risky
// Better approach - Type guard
if (user !== null) {
console.log(user.name); // Safe
}
⚠️ Warning:
Use the non-null assertion operator (!) sparingly. It bypasses TypeScript's safety checks and can lead to runtime errors if the value is actually null or undefined.
🔹 Nullish Coalescing (??)
Provide default values for null or undefined:
// Nullish coalescing operator (??)
let username: string | null = null;
let displayName = username ?? "Guest";
console.log(displayName); // "Guest"
username = "Alice";
displayName = username ?? "Guest";
console.log(displayName); // "Alice"
// Difference from || operator
let count: number | null = 0;
let result1 = count || 10; // 10 (0 is falsy)
let result2 = count ?? 10; // 0 (0 is not null/undefined)
console.log(result1); // 10
console.log(result2); // 0
🔹 Optional Chaining (?.)
Safely access nested properties:
interface Address {
street: string;
city: string;
}
interface User {
name: string;
address?: Address;
}
const user1: User = {
name: "Alice",
address: { street: "123 Main St", city: "Boston" }
};
const user2: User = {
name: "Bob"
};
// Without optional chaining
// console.log(user2.address.city); // Error!
// With optional chaining
console.log(user1.address?.city); // "Boston"
console.log(user2.address?.city); // undefined
// Combine with nullish coalescing
const city = user2.address?.city ?? "Unknown";
console.log(city); // "Unknown"
🔹 Definite Assignment Assertion
Tell TypeScript a variable will be assigned:
class UserService {
private user!: User; // Definite assignment assertion
constructor() {
this.initialize();
}
private initialize() {
this.user = { id: 1, name: "Alice" };
}
getUser(): User {
return this.user; // No error
}
}
// Without assertion
class BadService {
private user: User; // Error: Property 'user' has no initializer
constructor() {
this.initialize();
}
private initialize() {
this.user = { id: 1, name: "Alice" };
}
}
🔹 Best Practices
- Enable strictNullChecks: Always use strict null checking
- Use Type Guards: Check for null/undefined before using values
- Prefer Optional Chaining: Use ?. for safe property access
- Use Nullish Coalescing: Provide defaults with ??
- Avoid ! Operator: Use type guards instead of assertions
- Be Explicit: Clearly indicate when values can be null/undefined
🔹 Practical Example
interface Product {
id: number;
name: string;
price: number;
discount?: number;
}
class ShoppingCart {
private items: Product[] = [];
addItem(product: Product | null): void {
if (product === null) {
console.log("Cannot add null product");
return;
}
this.items.push(product);
}
getTotal(): number {
return this.items.reduce((total, item) => {
const discount = item.discount ?? 0;
return total + (item.price - discount);
}, 0);
}
findItem(id: number): Product | undefined {
return this.items.find(item => item.id === id);
}
}
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: "Laptop", price: 1000, discount: 100 });
cart.addItem({ id: 2, name: "Mouse", price: 50 });
cart.addItem(null); // Cannot add null product
console.log(`Total: $${cart.getTotal()}`); // Total: $950
const item = cart.findItem(1);
console.log(item?.name ?? "Item not found"); // Laptop