JavaScript is an unusual programming language in that it has two null-like values: undefined
and null
. These are distinct values (null
!== undefined
). They are also different data types in JavaScript, among the primitive types are string
, number
, boolean
, null
and undefined
.
After a couple of years of working with JavaScript, I came to the conclusion that null
is pretty problematic and should be avoided when writing JavaScript whenever possible. I’ve since spent several years writing both Node.js and various browser-based JavaScript applications without using null
(except for converting to or from undefined:
if (funcReturnValue === null) {
funcReturnValue = undefined;
}
JavaScriptI’ve found it to be a viable and desirable approach to take.
Before discussing why it seems better to avoid null
, let’s first talk about how these values, undefined
and null
get into your application’s data flows to start with.
Where Undefined and Null Come From
undefined
is omnipresent in JavaScript. If you declare a variable without an explicit value assigned to it, it gets the value undefined
:
let myVariable; // myVariable is undefined
JavaScriptIf you do not pass a parameter to a function, and you access that parameter in the function, it will have the value undefined
:
function myFunction(a) {
console.log(a);
}
myFunction(); // this will print out "undefined"
JavaScriptIf your function returns nothing, and the return value is assigned to a variable, that variable will receive the value undefined
:
function myFunction() {
// do nothing!
}
let myVariable = myFunction(); // myVariable will have the value undefined.
JavaScriptIf you access an element of an array that has nothing in it, you will also get undefined
:
let myArray = [1, 2];
let myVariable = myArray[5]; // myVariable is now undefined
JavaScriptNaturally, you can also explicitly set something to undefined:
let myVariable = undefined;
JavaScriptWhile there are other obscure ways to generate undefined
, such as let myVariable = void 0
, the previously listed cases are the most common ways.
In contrast, null
never comes as a result of no value being assigned to a variable or parameter. You must always explicitly set something to null
. This happens either through an explicit assignment:
let myVariable = null;
JavaScriptCalling a browser function that returns null
:
let myVariable = localStorage.getItem("aKeyThatDoesNotExist");
JavaScriptOr deserialization a JSON value:
let myVariable = JSON.parse('{"a":null}').a;
JavaScriptWhy it is Desirable to Avoid Null
Supporting both null
and undefined
creates more work if you are routinely operating with both null
and undefined
in the data flows in your application, you are forced to do one of three things:
- You must be studiously aware of which value is going through each data flow in your application
- This is unrealistic in all but the most trivial of applications. (See the note about TypeScript below)
- You must routinely be checking for both in your data flows
- e.g.
if (myValue === undefined || myValue === null) {…}
. This makes your code harder to read.
- e.g.
- You must use the regular equality operators
- For example,
if (myValue != null) {…}
, rather than the type-safe equality operators,if myValue !== null {…}
- For example,
This is definitely a risky pattern. There are eslint rules that can help you do the right thing here (e.g. "eqeqeq": ["error", "always", {"null": "ignore"}]
), but at best this introduces what your eyes will see as unnecessary variability in your code. At worst (if you are not using eslint’s rule), it will introduce bugs that you will overlook in code reviews.
Supporting Only null
Is Not Easy
Because undefined
is so extremely easy to introduce into your data flows, it takes substantial work to keep it out. If the goal is to get the highest quality of code with the minimal amount of work, this is not a good candidate for meeting that goal.
Supporting Only undefined
Is Not Hard
In practice, it is easy to capture the null
values any time that they would get introduced into your code and immediately switch that value to undefined. e.g. for function return values you can do something like:
let myValue = localStorage.get("aKeyThatDoesnotExist") || undefined;
JavaScriptFor both browser and third party API’s, you can often figure out if a function returns (or expects) null
from the documentation and convert, as above (though, you do need to be careful because both ""
(empty string) and false will also be converted to undefined
in the example above). If there are TypeScript definitions for your library, that can also help considerably, since TypeScript can make explicit the types libraries use. One common case is dealing with JSON data structures (unlike JavaScript, JSON has no notion of undefined
). But a simple conversion routine solves this:
let replaceValues = (data, oldValue, newValue) => {
if (Array.isArray(data)) {
return data.map((element) => replaceValues(element, oldValue, newValue));
}
if (typeof data === "object" && data !== null) {
return Object.entries(data).reduce((prev, [key, value]) => {
return {
...prev,
[key]: replaceValues(value, oldValue, newValue),
};
}, {});
}
return data === oldValue ? newValue : data;
};
JavaScriptSimilar things can be done for return values from third-party functions.
Null Doesn’t Trigger Default Values
If you have a function function myFunction(x = 5)
, when you call myFunction()
or myFunction(undefined)
, then x
will be set to 5
. But if you call myFunction(null)
, then x
will be set to null
. If you have variables that could be indiscriminately set to either null or undefined
(a situation which is very likely if you are allowing both into your application code), these default values will not always be applied as you are likely to want them to be applied.
On the other hand, if you are treating null
and undefined
distinctly, then you may actively find it useful to be able to not get that default value by deliberately passing null
. But as mentioned elsewhere, the effort to make this safe doesn’t seem like a good tradeoff to make.
typeof null === "object
“
One of the most frustrating things about null
is that typeof null === "object"
. This means that if you do allow null
into your data flows, when you do typeof
on any variable that might receive null
, you must check whether an object
result means null
or some form of {…}
.
Similarly, if you are checking whether something is an object, you must then make sure it isn’t a null
before you dereference it.
undefined
has none of these problems, since typeof myValue === "undefined"
is not ambiguous.
A Note About TypeScript
If you use TypeScript and use it with a great deal of discipline, it is much more realistic to manage both null
and undefined
flowing through your application. However, what this will also create for you is many situations where some data structures have null
values in one part of the application, and their equivalents in other parts of the application will have undefined
. Because these will collide and conflict, you’ll either end up with type declarations like myProperty: null | undefined
or you will end up having to do a lot of data conversions at various unpredictable places in your application. In the end, while the explicit treatment of these types is a big improvement over JavaScript, the hassle factor remains unchanged (or even worse). Even with TypeScript, then, it still seems better to simply keep null
out of the data flows in your application.
Conclusion
Because undefined
is, effectively, unavoidable, because null
is pretty easy to keep out of the data flows in an application, and because the code is simpler by not needing to manage both data types, I’ve found it more productive just to ignore the existence of null
except at necessary interface boundaries where we simply convert to undefined
.
Get Split Certified
Split Arcade includes product explainer videos, clickable product tutorials, manipulatable code examples, and interactive challenges.
Switch It On With Split
The Split Feature Data Platform™ gives you the confidence to move fast without breaking things. Set up feature flags and safely deploy to production, controlling who sees which features and when. Connect every flag to contextual data, so you can know if your features are making things better or worse and act without hesitation. Effortlessly conduct feature experiments like A/B tests without slowing down. Whether you’re looking to increase your releases, to decrease your MTTR, or to ignite your dev team without burning them out–Split is both a feature management platform and partnership to revolutionize the way the work gets done. Switch on a free account today, schedule a demo, or contact us for further questions.