4.2 undefined and null: Two Flavors of Nothing
Right, so you’ve met undefined and null. You’re probably thinking, “Why on earth does one language need two ways to say ’nothing’?” It’s a fair question. The official, committee-approved answer is a load of semantic nonsense about intent. The real, historical answer is that JavaScript was designed in ten days, and this is one of the warts that stuck. Don’t worry, we’ll make sense of it. Think of them as two distinct flavors of nothing: undefined is the nothing you get by accident, and null is the nothing you assign on purpose.
The Accidental Nothing: undefined
undefined means a value hasn’t been assigned. It’s the language’s default empty value. It’s what you get when you look for something that isn’t there. It’s the void staring back at you.
Here are the most common ways you’ll stumble into undefined:
// 1. An uninitialized variable (in modern code, use let/const)
let myVariable;
console.log(myVariable); // undefined
// 2. A missing function parameter
function greet(name) {
console.log(`Hello, ${name}`);
}
greet(); // Hello, undefined
// 3. An object property that doesn't exist
const myObject = { a: 1 };
console.log(myObject.b); // undefined
// 4. The return value of a function that doesn't return anything
function noReturn() {
// does nothing
}
console.log(noReturn()); // undefined
undefined is a primitive type, and it’s a value in its own right. Crucially, undefined == undefined is, unsurprisingly, true. The modern way to check for it is with the strict equality operator (===) or by using typeof.
let nothing;
if (nothing === undefined) {
console.log("It's undefined"); // This will run
}
if (typeof nothing === 'undefined') {
console.log("This is also true"); // So will this
}
The typeof check is particularly bulletproof because it works even if someone has, for some horrifying reason, overwritten the global undefined variable (which was possible in older browsers).
The Intentional Nothing: null
null is different. It’s also a primitive value, but it signifies intentional absence. It’s you, the programmer, explicitly saying, “This thing has no value.” You have to actively put null somewhere; it never appears by the language’s own accident.
// I'm explicitly saying this user has no middle name
const user = {
firstName: 'Alice',
middleName: null,
lastName: 'Smith'
};
// A function might return null to explicitly indicate "no result found"
function findUser(id) {
// ...lookup logic...
if (!userExists) {
return null; // I am deliberately returning nothingness
}
return user;
}
This is the philosophical distinction: undefined is a symptom of the system’s state, while null is a meaningful value you use in your program’s logic.
The Million-Dollar Mistake: Loose Equality
Here’s where the designers truly gifted us a nightmare. The abstract equality operator (==) does a famously absurd thing: it considers null and undefined to be equal.
console.log(null == undefined); // true
console.log(null === undefined); // false (this is what you should use)
This is a historic artifact of a bad idea called “type coercion.” The rule is: when comparing them loosely, null and undefined only equal each other and themselves. They don’t coerce to any other value. But you should never, ever rely on this. Always use strict equality (=== and !==). This one rule will save you from countless bizarre bugs. The loose equality check here is a trap designed to lull you into a false sense of security before your code explodes in production.
Practical Pitfalls and How to Defuse Them
The real danger isn’t the values themselves, but how they interact with the rest of the language. Both are “falsy,” meaning they behave like false in boolean contexts, which is useful.
if (undefined || null) {
console.log("This will never run");
}
The big gotcha is trying to access properties of null or undefined. This throws a TypeError and will halt your script in its tracks.
let empty = null;
console.log(empty.someProperty); // TypeError: Cannot read properties of null
let notDefined;
console.log(notDefined.someProperty); // TypeError: Cannot read properties of undefined
This is why the optional chaining operator (?.) is the best thing to happen to JavaScript since sliced bread. It’s your defensive shield against this exact problem.
const user = findUser(12345); // This might return null
// The old, verbose way:
let email = user && user.contact && user.contact.email;
// The new, brilliant way:
let email = user?.contact?.email; // If any part is null/undefined, it short-circuits to undefined
console.log(email); // undefined (no error thrown!)
Best Practice: Use null to signify intentional, meaningful absence in your own functions and APIs. For everything else, assume undefined is the default state of the uninitialized world. And for checking either, use strict equality or optional chaining. It’s not elegant, but it’s the reality of the trench warfare of JS development. You just have to know your enemy.