21.2 strictNullChecks: Making null and undefined Explicit
Alright, let’s talk about strictNullChecks. This is the single most important compiler flag in TypeScript, and if you have it turned off, you’re basically driving a car with the airbags disconnected because you find the warning light annoying. I’m not judging. Okay, I’m judging a little. Turn it on. Right now. I’ll wait.
Without strictNullChecks, TypeScript treats null and undefined as valid values for every type. It’s the wild west. A string isn’t just a string; it’s a string that might also be null or undefined. This is, to use a technical term, bonkers. It completely undermines the entire point of using a type system to catch errors before they happen at runtime.
Enabling this flag changes the rules of the game. Suddenly, null and undefined are no longer implicit members of every club. They become their own distinct types. A variable of type string is now just a string. If you want it to be able to hold a null or undefined, you have to say so explicitly using a union type, like string | null. This forces you to confront the possibility of absence head-on, which is exactly what we want.
The Billion-Dollar Mistake Meets Its Match
Tony Hoare, the inventor of the null reference, famously called it his “billion-dollar mistake.” strictNullChecks is TypeScript’s primary mechanism for helping us clean up that mess. It shifts null and undefined from being silent, runtime landmines to being compile-time errors. The compiler will now yell at you when you try to use a value that might be null or undefined without first checking for it.
Consider this classic JavaScript blunder that we’ve all written:
// With strictNullChecks: false (The Bad Old Days)
function getLengthBad(s: string) {
return s.length; // "s" could be undefined here, but TS doesn't care!
}
const maybeString = Math.random() > 0.5 ? "hello" : undefined;
getLengthBad(maybeString); // Compiles fine. Explodes at runtime: Cannot read properties of undefined.
Now, watch what happens when we enable our guardian angel:
// With strictNullChecks: true (The Enlightened Path)
function getLengthGood(s: string) {
return s.length;
}
const maybeString = Math.random() > 0.5 ? "hello" : undefined;
getLengthGood(maybeString);
// ~~~~~~~~~~~~
// Error: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
The compiler stops us before the code ever runs. It’s like having a brilliant, hyper-vigilant friend constantly looking over your shoulder saying, “Hey, are you sure about that?”
Taming the Beast: Working with Optional Values
So you have a value that might be null or undefined. Now what? The compiler will force you to handle both possibilities. You have two main weapons: type guards and the power of truth.
1. Type Narrowing with Truthiness Checks: The simplest and most common way. A simple if check is enough to narrow the type within the subsequent block.
function printLength(s: string | null | undefined) {
if (s) { // Truthy check: rules out null, undefined, and empty string
console.log(s.length); // Type of "s" is now 'string'
} else {
console.log('You gave me nothing!'); // Type of "s" is still 'string | null | undefined'
}
}
printLength("hello"); // Prints "5"
printLength(null); // Prints "You gave me nothing!"
2. Explicit Equality Checks: Sometimes you need to be more precise, especially if an empty string "" is a valid value for you.
function printLengthPrecise(s: string | null | undefined) {
if (s !== null && s !== undefined) {
console.log(s.length); // Type of "s" is now 'string'
}
// Or, more concisely: if (s != null) { ... }
// Note: != null checks for both null AND undefined. It's a useful shortcut.
}
The Non-Null Assertion Operator (The Escape Hatch)
Sometimes, you, the human, know more than the compiler. You might have a variable that is string | null but you know for a fact that by a certain point in the code, it will never be null. For these rare cases, TypeScript gives you the non-null assertion operator (!). It’s a postfix exclamation mark that tells the compiler, “Shut up, I know what I’m doing.”
function definitelyAssignsValue(): string | null {
return "I'm definitely a string";
}
const myValue = definitelyAssignsValue();
// We know the function above always returns a string right now.
console.log(myValue!.length); // Using ! to assert it's not null/undefined
WARNING: Use this operator sparingly. It’s a sledgehammer that silences the compiler. If you’re wrong, you’ve just reintroduced a runtime error. It’s for when you have genuinely superior knowledge, not for when you’re too lazy to write a proper type guard.
The Optional Chaining and Nullish Coalescing Duo
ES2020 gave us two syntax features that are a perfect complement to strictNullChecks. They are so useful that they feel like they were designed for this exact purpose.
Optional Chaining (?.): Lets you safely access deeply nested properties. If the value before the ?. is null or undefined, the expression short-circuits and returns undefined.
interface User {
name: string;
address?: {
street: string;
zipcode?: string;
};
}
const user: User = { name: "Bob" }; // No address!
// Without optional chaining (verbose)
const zipcode = user.address && user.address.zipcode;
// With optional chaining (bliss)
const zipcode = user.address?.zipcode; // Type is 'string | undefined'
// If user.address is null or undefined, it stops and returns undefined.
Nullish Coalescing (??): Provides a default value only when the left-hand side is null or undefined. This is different from the logical OR (||) operator, which uses truthiness (so || would also catch 0, "", and false).
const input = undefined;
const value1 = input || 'default'; // Uses 'default' (|| checks truthiness)
const value2 = input ?? 'default'; // Uses 'default' (?? checks nullishness)
const count = 0;
const count1 = count || 10; // 10 (because 0 is falsy)
const count2 = count ?? 10; // 0 (because 0 is not null or undefined)
Together, ?. and ?? allow you to write concise, robust, and intention-revealing code for handling optional values. They are the perfect partners for the world strictNullChecks creates. Embrace them.