Undefined vs. Null

In the years that I've spent reading about and writing in JavaScript I've come across a lot of confusion around undefined and null. On the surface, they might seem interchangeable. In fact, undefined and null share a number of similarities:

  • Both are Primitives in JavaScript
  • Both represent a lack of value
  • Both are Falsy Values
    • Because of this, when we do a loose-equals comparison of undefined == null, the language returns true

If they loosely evaluate to be the same thing, it's not a major stretch to think that they are completely interchangeable. That's not the case though.

The key practical difference is that undefined is most commonly seen as the value that the JavaScript compiler assigns to a variable when a variable is declared, but not given a value. null on the other hand must be deliberately assigned as the value of a variable by the developer. The language and the compiler will never assign anything the value of null.

So that's the short explanation, but I'm not content to stop there. Let's once again dig deep into the language and its fundamentals to really understand what undefined and null really are, how they behave, what sets them apart from one another, and when you might encounter or use them.

Primitive Types in JavaScript

In JavaScript, there are nine data and structure types. These can be further broken down into the following categories:

Six Primitive Data Types:

  1. undefined
  2. Boolean
  3. Number
  4. String
  5. BigInt
  6. Symbol

Two Structural Types:

  1. Object
  2. Function

One Structural Root Primitive:

  1. null

The focus of this blog post isn't on each of these data types, but instead on undefined and null, which at an initial glance are very similar.

From the list above, undefined and null stand out as values that both denote a lack of value. With a Number, you can imagine values such as 1 or 100 or 1000. With a String you might imagine something like "Joey drove his car" or "Slightly Overcast" or "Red". With an Array you might imagine a list like ["eggs", "bacon", "butter", "milk"], and so on for the other Primitives. With undefined and null, conceptually they seem to represent non-value, some kind of void.

Going further, in his book You Don't Know JS: Types & Grammar, Kyle Simpson points out something interesting about these two Primitives:

For the undefined type, there is one and only one value: undefined. For the null type, there is one and only one value: null. So for both of them, the label is both its type and its value.

The lack of value also feels related to another concept in JavaScript, the concept of Falsy Values.

Falsy Values

A Falsy Value is a value that evaluates to false when used in a Boolean context. What does this mean? Well let's take a look at the following:

const daylightSavingsTime = false;

if (daylightSavingsTime) {
  console.log("We are one hour back"); // This doesn't run
}

The code inside of the if block does not run because daylightSavingsTime is false. We are using a plain Boolean value here, but we can do similar work with non-Boolean values that evaluate to false. Here's an example:

const string = ""; // Empty string

if (string) {
  console.log(string); // This doesn't run
}

Again, the block within the if statement only runs if the condition that has been passed evaluates to true. We passed in an empty string, which evaluates to false, and the code in the block does not run. The empty string evaluates to false because an empty string is a Falsy Value.

There are six Falsy Values in JavaScript:

  1. false (Boolean)
  2. 0 (the Number 0)
  3. "" (an empty String)
  4. null
  5. undefined
  6. NaN (Not a Number)

Again, this blog post is not focusing on Falsy Values, but instead I wanted to highlight the topics of null and undefined and the fact that they are both Primitive Types and Falsy Values in JavaScript.

What is undefined?

undefined is a type that occurs when a variable is declared but not assigned a value. Consider the following code:

let ingredients = ["bacon", "eggs", "butter"];
console.log(ingredients); // ["bacon", "eggs", "butter"]

let recipe;
console.log(recipe); // undefined

First we create an ingredients variable by declaring it. Then, using the = equals sign, assign it the value of an array containing a few strings.

When we console.log ingredients, the console prints out the array and its contents. Pretty simple.

Below that we create a recipe variable, but do not assign any value to it. recipe exists in name space, but it contains no data. Because of this, when we console.log recipe, the console returns undefined.

This code demonstrates the most common way that an undefined value or type will be encountered, which is when a variable has not been assigned a value.

Per MDN:

A variable that has not been assigned a value is of type undefined. A method or statement also returns undefined if the variable that is being evaluated does not have an assigned value. A function returns undefined if a value was not returned.

So the prevailing pattern is that undefined is something the language compiler assigns to values that are declared, but not given any other value. That's true, but it should also be pointed out that you can deliberately set a variable to equal undefined:

let recipe = undefined;
console.log(recipe); // returns undefined

However this practice seems to be something of an anti-pattern and I would advise against it. More on this later, but if you feel drawn to deliberately assign a lack of value to a variable, I think it is best to use null.

You may also encounter undefined if you are trying to access non-existent properties on an object:

const person = {
  name: 'Joey',
  sayHi: function() {
    console.log(`Hi ${this.name}`);
  },
}

console.log(person.name); // "Joey"
console.log(person.age); // undefined - no 'age' property

typeof and undefined

I would like to take a moment to note the behavior of the typeof operator with an undefined variable.

let recipe;
console.log(typeof recipe); // returns undefined

As you'll see in this example, typeof, when used with an undefined value, returns undefined. This may seem like a tautology, but I want to highlight the way that typeof can cause some confusion.

There's an important distinction to be drawn between an "undefined" variable and an "undeclared" variable, since these two terms sometimes get used interchangeably. As I've said before, "undefined" refers to a variable within the accessible scope that has not been assigned a variable. However, an "undeclared" variable is one that has not been declared or created within scope. Consider this example:

var name;
name; // undefined
age; // ReferenceError: age is not defined

When calling a variable that has been declared but not assigned a value, undefined is returned. However, when calling a variable that has been neither declared nor assigned a value, a ReferenceError is thrown, stating that the variable is "not defined". This wording is unfortunate, since linguistically "undefined" and "not defined" mean the same thing in English.

Furthering the confusion is how typeof works with the same two variables:

var name;
console.log(typeof name); // undefined
console.log(typeof age); // undefined

Huh. So typeof will return undefined for both undefined and undeclared variables. Again I'm simply writing all of this to illustrate how there could be confusion surrounding "undefined" variables and "undeclared" variables.

undefined as an Identifier

Let's go even further into the weeds! undefined is technically an identifier (whereas null is a keyword). This means that in non-strict mode, it's possible to assign a value to the global undefined identifier.

function badIdeaNeverDoThis() {
  undefined = 100; // I can't tell you how bad of an idea this is
}

badIdeaNeverDoThis(); // global undefined has been reassigned

strict mode does not allow this global identifier to be reassigned.

function badIdeaNeverDoThis() {
  "use strict";
  undefined = 100; // TypeError
}

badIdeaNeverDoThis();

However in both non-strict mode and strict mode you can create a local variable named undefined. This is also a really bad idea, don't do this.

function badIdeaNeverDoThis() {
  "use strict";
  var undefined = 100;
  console.log(undefined); // 100
}

badIdeaNeverDoThis();

Void

Another way to get undefined is through the void operator. This operator always returns undefined without modifying the existing value.

var age = 33;
console.log(age); // 33
console.log(void age); // undefined

This is a bit of a niche language feature, but I imagine it could have interesting uses when working with certain templating languages. Handlebars comes to mind.

What is null?

null is an assignment value that can be assigned to a variable to represent that that variable holds no value.

let empty = null;
console.log(empty); // returns null

So null as a value represents something that is empty or non-existent, and null must be deliberately assigned within the code. The compiler will not automatically set something to null.

Per MDN:

The value null is written with a literal: null. null is not an identifier for a property of the global object, like undefined can be. Instead, null expresses a lack of identification, indicating that a variable points to no object. In APIs, null is often retrieved in a place where an object can be expected but no object is relevant.

typeof and null

I also want to draw attention here to how the typeof operator works with a null value:

let empty = null;
console.log(typeof empty); // returns object

Where typeof undefined returns undefined, typeof null returns object. This is widely regarded as a bug in the language that has existed for a very, very long time. It's been a bug for so long that a lot of code out on the web relies on typeof null to return object, and fixing the bug would create even more bugs and break all kinds of currently running applications.

Comparing null and undefined

So what happens if we start comparing these two types directly?

What do we get if we compare using loose-equals comparison?

null == undefined; // true
undefined == null; // true

A loose-equals comparison, which allows for type-coercion, yields true, since these are both Falsy Values.

What happens if we do a strict-equals comparison, which doesn't allow for type-coercion?

null === undefined; // false
undefined === null; // false

Unsurprisingly, when type coercion isn't allowed, the two values are deemed to not be strictly equal to one another, since they are of different types.

Default Parameters

Another interesting (and more practical) comparison comes by way of Tim Branyen, though I found it in Brandon Morelli's JavaScript — Null vs. Undefined article. It comes into play when using default parameters in a function call.

Let's take a look at the following function, which will greet you by name.

function sayMyName(name = 'Joey') {
  console.log(`Howdy ${name}`);
}

If we call the function as it is, it will use the string "Joey" as the default value for the name parameter.

sayMyName(); // "Howdy Joey"

We can also pass in a new name as an argument, overriding the default, like so:

sayMyName("Bart"); // "Howdy Bart"

So what happens when we pass undefined or null as the argument?

sayMyName(undefined); // "Howdy Joey"
sayMyName(null); // Howdy null

Passing undefined will fall back to the default parameter value, where null will simply use null.

Nullish Values

If you're still with me and not totally lost yet then I both commend you and invite you to venture even further into the weeds with me. Let's talk about Nullish Values.

Per MDN:

In JavaScript, a nullish value is the value which is either null or undefined. Nullish values are always falsy.

That is the totality of their article on Nullish Values. It seems to be less of a type classification and more a loose specification that something is a non-value that is represented either by undefined or null. It seems to mostly come into play with some of JavaScript's less-commonly used features, like Optional Chaining (.?) and the Null Coalescing Operator (??).

I'll admit that I have not used either of these features in production, so what I represent here may not be the most complete or experienced picture, but the spirit of this blog is to dive as deep as possible, so let's go.

Optional Chaining (.?)

Optional Chaining, represented in the language by a period and question mark (.?), is a way to safely access Nullish Value properties in an object, that is, those that may be undefined or null. As async programming has become more popular (think of all of the React applications out there that rely on data fetched from API calls), some common problems have emerged:

  • Data may not be available yet. Sometimes your API is slow or a certain async function hasn't resolved just yet
  • Data may be so deeply nested within sub-arrays and sub-objects that locating that data can be error-prone

Using default values and default props can help with some of these issues, but with classic JavaScript access methods, one of the issues listed above may just throw an error that crashes your entire application.

For some time developers were using Lodash to simply return undefined as a safe, catch-all response if these kinds of errors were encountered. Optional Chaining enables this behavior without the need for an external library.

Read more about Optional Chaining in this FreeCodeCamp article.

Null Coalescing Operator (??)

The Null Coalescing Operator is represented by two question marks (??), which separates two expressions. If the first, left hand, expression is Nullish (i.e. either undefined or null), then the second, right hand, expression gets returned. If the first, left hand, expression is anything other than undefined or null, then that is the expression that gets returned.

Here's an example:

const nullThing = null;
const falsyThing = "";
const string = "Here's a string";

const firstTest = nullThing ?? string;
console.log(firstTest); // "Here's a string"

const secondTest = falsyThing ?? string;
console.log(secondTest); // ""

In the case of firstTest, the left hand expression is a null value, so the right hand expression gets returned.

In the case of secondTest, the left hand expression, an empty string, is a Falsy Value, but not undefined or null, so the Nullish Coalescing Operator returns the empty string.

Contrast this behavior with the better-known Logical Or Operator (||), which returns the right hand expression if the left hand expression is any Falsy Value:

const nullThing = null;
const falsyThing = "";
const string = "Here's a string";

const firstTest = nullThing || string;
console.log(firstTest); // "Here's a string"

const secondTest = falsyThing || string;
console.log(secondTest); // "Here's a string"

Since in both the firstTest and secondTest the left hand expression is a Falsy Value, the right hand expression is returned.

Read more about the Nullish Coalescing Operator on MDN.

When to Use undefined or null

So when should you use undefined and when should you use null?

The rule that I try to follow is that I never set anything as undefined myself. I will often create a variable without assigning it a value, at which point the JavaScript compiler will designate it as undefined. This is not something I consciously do in my code.

If I need to create a variable and deliberately designate it as not having a value, I will use null.

Summary

So let's put it all back together.

undefined and null share a number of similarities:

  • undefined and null are both Primitives
    • They are the only two Primitives where the label is the same as the value
  • undefined and null are both Falsy Values
    • undefined == null will return true as loose equality allows for type coercion
  • undefined and null both represent non-Value in an abstract kind of way
    • Because of this, a special classification called "Nullish Values" encompasses anything that is either undefined or null. This mostly seems to come into play when using Optional Chaining (.?) and the Null Coalescing Operator (??)

But that's about where the similarities end. There are differences in applications and uses between undefined and null:

  • undefined represents the value of a variable that has been declared, but not assigned a value. The JavaScript compiler will designate a variable as undefined that has not been given a value
    • There is a difference between an "undefined" variable and an "undeclared" variable, but the typeof operator and default language from a ReferenceError message can cause confusion
    • A programmer may also deliberately set the value of a variable to be undefined, but this is considered an anti-pattern
  • null is an assignment value representing a lack of any other value. A variable must be assigned the value of null by the programmer
  • undefined is an identifier, meaning that in certain contexts (non-strict mode and within local scoping), undefined can be reassigned to another value. This is not a good idea
  • null is a keyword and cannot be reassigned
  • Using the void operator will also always return an undefined value (regardless of, and without modifying, the original value of the data you are "voiding")
  • undefined === null will return false as strict equality does not allow for type coercion, and undefined and null are different types
  • typeof undefined will return undefined, but typeof null will return object. This latter detail is a long-standing error within the language
  • When used in conjunction with default parameters, passing undefined as a function's argument will cause the function to fall back to the default parameter. Passing null does not

Did I miss anything at all? Do you feel anything could be clarified or corrected in this deep dive? If you think so, please reach out to me by email or Twitter. I'm always happy to issue a correction or add any information and give credit or attribution. Or just let me know what you like or don't like about this post or any others!

Sources / Further Reading

Here are posts, articles, and documentation that I used in writing this blog post: