On a Bias Against Null in Javascript

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. e.g. if (funcReturnValue === null) { funcReturnValue = undefined; }), and have 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

If 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"
Code language: Delphi (delphi)

If 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.
Code language: JavaScript (javascript)

If 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
Code language: C# (cs)

Naturally, you can also explicitly set something to undefined:

let myVariable = undefined;

While 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;

Calling a browser function that returns null:

let myVariable = localStorage.getItem('aKeyThatDoesNotExist');

Or deserialization a JSON value

let myVariable = JSON.parse('{"a":null}').a;

Why it is Desirable to Avoid Null

  1. 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.
You must use the regular equality operators (e.g. if (myValue != null) { …, rather than the type-safe equality operators (e.g. if myValue !== null ... )
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;

For 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; };
Code language: JavaScript (javascript)

Similar 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.

What to Read Next

I hope you enjoyed reading our article, “On a Bias Against Null in JavaScript.” Here are some other posts you might enjoy:

For more great content, follow us on YouTube and Twitter! You can also join the Split Community on Slack.