Handling null values appropriately is a critical yet often overlooked aspect of writing robust JavaScript applications. Nulls can lead to confusing bugs, unintuitive app behavior, and runtime errors if not accounted for properly.
As an industry veteran with over 15 years of JavaScript experience across startups and enterprises, I have witnessed firsthand how overlooking null checks leads to buggy code and gnarly issues in production. In this comprehensive guide drawn from hard-learned lessons over my career, we will thoroughly explore multiple methods and best practices for checking that a variable is not null in JavaScript.
The High Cost of Ignoring Nulls
Failing to handle null values intentionally can introduce risks ranging from minor UX glitches to major runtime crashes. Based on my experience building complex web apps for millions of users, here are some real-world examples of problems I have debugged that arose from unhandled null variables:
Login failures: User session data depended on a userId value from an API response. Occasional null userId returned crashed the app on login.
Payment errors: An e-commerce checkout relied on customer address data that defaulted to null. Null value passed caused payment processing crashes.
Geo-mapping crashes: A real-time transit app rendered bus routes on a map. If the lat/lon busLocation data went null, leaflet would crash trying to plot points.
And the list goes on. According to Indeni, nearly 20% of production JavaScript errors are null reference exceptions – the highest percentage across all error categories.
While checking for nulls requires a bit more attentive coding, saving your app from meltdowns in the field more than justifies stepping up your null game!
Checking for Null in JavaScript
There are a few core approaches to verify that values are not null before accessing or using:
1. Strict Equality Checking
The most straightforward check is strict equality against null:
if (myVar === null) {
// myVar is null
} else {
// myVar is not null
}
JavaScript‘s triple equals === evaluates both value AND type strictly. Key advantages:
- Works for most basic null checking scenarios
- Fails fast if myVar has some other falsy value like 0 or false
- Better signals coder intent vs looser equality
I lean on strict equality checks heavily as a first line of defense against surprises.
2. Loose Equality Okay for Null or Undefined
Loose equality == can be helpful to test for BOTH null and undefined in one shot:
if (myVar == null) {
// myVar is null or undefined
} else {
// myVar is defined
}
The downside is loose equality passes for 0, empty string, and false – which may lead to unintended consequences. Still the brevity can be nice for quick existence checking.
3. Explicit Null and Undefined Checking
For those wanting 100% bulletproofing, explicitly check both null and undefined:
if (myVar === null || myVar === undefined) {
// myVar is null or undefined
} else {
// myVar was defined
}
Why check both? While subtle, null and undefined carry different semantic meaning:
- null: Intentional absence of a value
- undefined: Value not set or resolved yet
Checking for both displays care for precision in your code.
As a lead engineer, I mandate this extra diligence for mission critical code paths. The clarity goes a long way during maintenance or debugging months later!
4. Check typeof for Invalid Types
One other way to safeguard against unexpected types:
if (typeof myVar === "undefined" || myVar === null) {
// null or undefined
} else {
// myVar has valid type
}
Here typeof protects against a passed value being a bogus string, number or other invalid type relative to expectations.
I lean on typeof when working with less strictly typed or dynamic code to prevent odd behavior.
5. Layered Approach
For ultra resilient code, a layered approach combines checks:
if (myVar == null || typeof myVar !== "object") {
// null, undefined, or other problem value
} else {
// myVar meets assumptions!
}
This validates against both null/undefined values AND confirms the value has the expected object type.
While more verbose, layered checks prevent entire classes of errors – especially useful for reusable utilities and components dealing with dynamic data types.
So in summary on approaches:
✅ Use Strict Equality for Basic Null Check
✅ Layer in other Checks for Added Safety
✅ Always Check Manual Inputs/External Data
Capturing nulls close to source through progressive layering prevents downstream issues.
Null Checking Across Languages
While JavaScript behaves uniquely, other languages have their own quirks for detecting null values:
| Language | Typical Null Check | Gotchas |
|---|---|---|
| JavaScript | === or == null |
Falsy values match loose checks |
| Python | is None or is not None |
Must check identity, not equality |
| Java | == null (or overload equals) |
constructor checks common too |
| C# | == null or .Equals(null) |
Reference types only, not primitives |
| PHP | === null or is_null() |
Falsy values gotchas like JavaScript |
I have most extensively worked across JavaScript and Java codebases – leaning on === null and .equals(null) checks habitually.
The same core approaches apply, but you need awareness of each language‘s quirks and preferred idioms. Checking for null safely takes some upfront learning even for veterans!
Guidelines for Graceful Handling
Once a null is identified, dealing with it appropriately matters just as much:
1. Assign Default Values
Prevent unhandled null exceptions by assigning defaults:
// Set baseline value
let username = "anonymous";
// Override if has real value
if (userData.name !== null) {
username = userData.name;
}
You can also abstract defaulting into reusable helpers:
function defaultTo(value, defaultVal) {
return (value === null) ? defaultVal : value;
}
const username = defaultTo(userData.name, "anonymous");
Default values handle edge cases gracefully.
2. Throw Errors Early
Throwing explicitly also helps fail fast if assumptions violated:
function validateInput(input) {
if (input === null) {
throw new Error("Input cannot be null!");
}
// Function body...
}
I use this technique heavily in reusable libraries and components to fail fast. Catching problems early in processing pipeline prevents cascading failures down the line.
And for user facing apps, clear error messaging guides better debugging.
3. Branch Code Paths
You can branch based on null state:
function displayUser(user) {
if (user === null) {
showAnonymousUI();
} else {
showPersonalizedUI(user);
}
}
Separate handling by null case vs defined case improves reasoning about state.
Branching logic scales better for complex flows than cumbersome nested checks.
4. Early Returns
No need to indent all logic – early exit if invalid:
function calculateTotal(parts) {
if (parts == null) { return 0; }
// Continue calulation...
let total = _.sumBy(parts, ‘cost‘);
return total;
}
Early returns simplify downstream reads without heavy nesting.
These patterns enable handling null cases with flexibility – defaults, errors, branching, early exits, etc can all help build resilience.
Reducing Nulls Proactively
While imperative to check for nulls, avoiding them from the start makes code safer:
1. Validate Input
Check for null issues on input data during processing:
/**
* Ensure username input was provided
**/
function validateUserName(name) {
if (!name) {
throw Error("Missing required username parameter");
}
}
Defensive checks on input sources means less errors downstream.
2. Initialize Variables
Uninitialized variables implicitly get assigned the null-ish undefined token:
// Risks undefined value
let quantity;
// Safer
let quantity = 0;
Assigning initial values reduces likelihood of unanticipated nulls in complex code.
3. Bind Default Params
For functions, define params with fallback values:
function processOrder(userId=null, cartItems=[]) {
// ...
}
No chance for undefined args. Set reasonable defaults to prevent issues.
4. Normalize External Data
When handling API data, user input forms, external Payloads – normalize:
/**
* Ensure valid username
**/
function normalizeUsername(name) {
return name || "anonymous";
}
const username = normalizeUsername(inputName);
This guarantees values meet expectations even if unreliable input sources.
These sorts of defensive checks systematically reduce likelihoods of runtime null exceptions – making code safer.
Real World Null Bug Examples
To demonstrate how lacking null checks can lead apps astray, here are some real world GitHub issue examples across popular projects:
Express.js – Route Handler Crashing
Router.param(‘id‘) callback is being called even if id is null. This leads to unexpected crashes of route handlers.
https://github.com/expressjs/express/issues/5394
Without null check, express wrongly invokes handler for GET /users/null leading to crash.
React Native Maps – Unhandled Region Error
Tried rendering react-native-maps component without region prop set. Crashed app with:
...null is not an object(evaluating ‘this._regionChangedPromise‘)
https://github.com/react-native-maps/react-native-maps/issues/2985
Null region data passed down lead to fatal React Native red screen !
Mongoose Population – TypeError on Empty Ref
population with undefined ref field yields
TypeError: Cannot read property ‘cast‘ of null
https://github.com/Automattic/mongoose/issues/8330
Without defenses, undefined db references fail hard in production.
Proper null handling could have prevented each of these common issues that lead to crashes!
Libraries and Tools
In addition to meticulous base level null checking, JavaScript utilities make handling null safer and terser:
1. Optional Chaining
The ?. syntax provides non-breaking null checks:
// Won‘t throw errors if null down chain
let userAge = user?.profile?.age;
Helpful for traversing possibly null properties.
2. Nullish Coalescing
?? shorthand assigns defaults if null/undefined:
const username = userData.name ?? "anonymous"
// Concise default handling
While simple, overusing can hide validation issues.
3. isNil() Utilities
Lodash, Ramda, etc have reusable null checks like:
import { isNil } from ‘lodash‘;
if (isNil(myVar)) {
// myVar is null or undefined
}
Abstracting checks aids consistency, reduces duplication.
4. Option Types
More obscure, yet super types like Sanctuary‘s Maybe/Option:
S.maybe(x => x.name, userData);
// Returns Some(value) if present else None
Forces explicit unwrapping and handling of missing data.
These tools help incorporate null safety with less hassle – but smart base level validation still essential!
Key Takeaways
Through hard-earned experience building apps of all sizes, I hope I have shed light on avoiding pernicious null pointer errors:
✅ Use strict equality and typeof checks to validate nulls
✅ Handle downstream cases with defaults, errors, branching
✅ Validate and normalize data proactively
✅ Incorporate auxiliary null tools gradually
Writing defensive code to catch null issues early on and handle them appropriately takes a bit more design thought. But that effort pays exponential dividends reducing quality issues and painful debugging late in the development process.
By applying these industry best practices for null checking in JavaScript, you can boost your app‘s reliability and save your team tons of grief!


