We have all been there. It is 4 PM on a Friday. You are staring at a stack trace that makes no sense.
The error is the classic JavaScript killer: Cannot read properties of null (reading 'email').
You click the line number. It points to a React component trying to render a user profile. But the code looks fine. The user object should be there. Why is it null?
You trace the data back. It comes from a helper function. That helper calls a service. That service calls an API utility.
Five layers deep, you find the culprit. A function named getUser. It encountered a database error, but instead of telling anyone, it quietly returned null and let the application keep running.
This is called "Passing the Buck."
It is the single hardest type of bug to debug because the symptom (the crash) happens five minutes and ten files away from the cause (the database failure).
Today, we are going to fix this using The Law of Loudness.
The Junior Trap: "Zombie Mode"
As Junior engineers, we are terrified of crashing the application. We think that a crashing app is a failed app. So, when we write a function and something goes wrong (the DB is down, the API timeouts), our instinct is to catch the error, sweep it under the rug, and return a "safe" value.
We return null, false, or -1.
We enter Zombie Mode. We keep the application walking around half-dead.
The Silent Failure
When you returnnullsilently, you are not handling the error. You are outsourcing the error handling to the function that called you.
Now, every single place that uses your function must remember to check for null. If even one developer forgets? The app crashes later, and nobody knows why.
The Pro Move: The Law of Loudness
Senior engineers follow a different rule: If a function cannot do what its name says it does, it should throw an error immediately.
This is The Law of Loudness.
If I call a function named getUser(id), I expect a user. I do not expect null. If you can’t give me a user because the database is on fire, don't hand me an empty box and smile. Scream at me.
We want to Fail Fast. We use exceptions to protect the integrity of our data. If the data is bad, stop the factory line immediately.
The Code: From Silent Killer to Loud Protector
Let’s look at the getUser function.
The Trap: The Silent Failure
This code looks "safe" because it doesn't crash. But it creates a minefield for the rest of the application.
// BEFORE: The "Safe" Silent Failure
function getUser(id) {
// If the DB is down, we hide it.
if (!db.isConnected()) {
console.log("DB is down...");
return null; // <--- Passing the buck
}
const user = db.find(id);
// If user isn't found, we return null again.
// Is it null because they don't exist?
// Or because the DB is broken?
// The caller will never know.
return user || null;
}
// THE RESULT:
// The caller code assumes it got a user.
const user = getUser(50);
// CRASH happens here, 10 lines later.
console.log(user.profile.email);
The Fix: The Loud Failure
Here is how a Professional handles the same scenario. We stop execution the moment we know we cannot fulfill the contract.
// AFTER: The Law of Loudness
function getUser(id) {
// 1. Guard Clause for System Failure
if (!db.isConnected()) {
// Stop execution immediately. We know EXACTLY what broke.
throw new DatabaseConnectionError('Cannot fetch user: DB is down');
}
const user = db.find(id);
// 2. Guard Clause for Data Integrity
if (!user) {
// Differentiate between "System Broken" and "Not Found"
throw new NotFoundError(`User with ID ${id} does not exist`);
}
return user;
}
// THE RESULT:
// The crash happens INSIDE getUser.
// The stack trace points exactly to the line that failed.
// We know if it was a DB error or a missing user instantly.
Why This is Better
- Debugging Speed: When the app crashes, the stack trace points to
getUser, line 4. You know immediately that the DB is down. You don't waste 3 hours tracingnullvalues through the UI components. - Trust: When I call
getUser, I don't have to writeif (user !== null)every single time. I can trust that if the code execution proceeds past that line, I have a valid user. - Data Integrity: You prevent "Zombies"—half-formed data objects—from floating around your state management and causing weird bugs (like saving a user profile with an ID of
undefined).
Summary
Don't be afraid of the red text in the console. Red text is honest.
A silent null is a lie. It tells the system everything is fine when the house is actually burning down.
The Rule:
- If the error is expected (e.g., searching for a product that might not exist), return a specialized Result type or
nullonly ifnullis a valid state for your domain. - If the error is unexpected (network down, bad configuration, missing required data), Throw. It. Loud.
Stop writing code just to please the compiler.
This article was an excerpt from my handbook, "The Professional Junior: Writing Code that Matters."
It’s not a 400-page textbook. It’s a tactical field guide to unwritten engineering rules.
Top comments (0)