Flagship 2024 – Day 2 is live! Click here to register and watch now.

Why You Should Use Undefined Instead of Null in Javascript

Contents

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;
  }
JavaScript

I’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
JavaScript

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"
JavaScript

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

Naturally, you can also explicitly set something to undefined:

let myVariable = undefined;
JavaScript

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

Calling a browser function that returns null:

let myVariable = localStorage.getItem("aKeyThatDoesNotExist");
JavaScript

Or deserialization a JSON value:

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

Why 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:

  1. 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)
  2. 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.
  3. You must use the regular equality operators
    • For example, if (myValue != null) {…}, rather than the type-safe equality operators, 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;
JavaScript

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;
};
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.

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.

Want to Dive Deeper?

We have a lot to explore that can help you understand feature flags. Learn more about benefits, use cases, and real world applications that you can try.

Create Impact With Everything You Build

We’re excited to accompany you on your journey as you build faster, release safer, and launch impactful products.