DEV Community

Bhushan Shah
Bhushan Shah

Posted on

undefined vs. null

Have you ever wondered why JavaScript (and TypeScript) has two distinct keywords — undefined and null — that kind of feel similar? Most other languages have a null (or equivalent) keyword. It's usually a value you can assign to a variable, and it indicates an "empty" value, or a "sentinel" value. It works similarly in JavaScript too. But then why does JavaScript have another similar keyword called undefined? What's the difference between the two? How do we decide if we should use undefined or null? How does the usage of undefined or null impact types? This post will try to provide answers to these questions.

Let's get into it.

The main conceptual difference between variables with value null and variables with value undefined is the intention of the programmer. The rule of thumb I like to follow is undefined is for unintentionally empty values, while null is for intentionally empty values. This means that any variable which has not been assigned a value has the value undefined, whereas if a programmer wanted to assign an "empty" value to a variable, they would typically assign it null, which is interpreted as a sentinel value.

let foo;
// `foo` is `undefined` because it's not set
console.log(foo); // undefined

let bar = null;
// `bar` is `null` because it is explicitly set to `null`
console.log(bar); // null

let obj = { baz: undefined };
// `obj['baz']` is `undefined` because it is explicitly set to `undefined`
console.log(obj['baz']); // undefined
// `obj['qux']` is `undefined` because it's not set
console.log(obj['qux']); // undefined
Enter fullscreen mode Exit fullscreen mode

Wait, this is too confusing...

Let's go through an example to see what this means.

Suppose we are writing some router logic, and want to add a Cache-Control header to the response if some flag isCachingOn was true.

We could do this:

let headers = {};
if (isCachingOn) {
  headers['Cache-Control']: 'max-age=31536000'
}
res.set(headers);
Enter fullscreen mode Exit fullscreen mode

But we could also rewrite this more succinctly as

res.set({
  'Cache-Control': isCachingOn ? 'max-age=31536000' : undefined
});
Enter fullscreen mode Exit fullscreen mode

Both these code snippets are equivalent. In both cases, if isCachingOn is false, we don't set the value of the Cache-Control property, which means we just end up passing {} as the argument to res.set(). Generally, setting something to undefined has the impact of "not setting" it (or "unsetting" it if already set).

But what if we set it to null instead?

res.set({
  'Cache-Control': isCachingOn ? 'max-age=31536000' : null
});
Enter fullscreen mode Exit fullscreen mode

Is this the same as the previous two snippets?

The answer is no.

In this case, if isCaching is false, we actually set the value of the Cache-Control property to null. This is not the same as "not setting" it. We are explicitly setting it to a null value. We end up passing { 'Cache-Control': null } to res.set(). Depending on what router framework we are using, this may very well work the same as the undefined example above, if the framework is smart enough to ignore headers whose value is set to null. However, from the perspective of the programming language, both are different, and this difference may be much more important in some cases.


What about types though?

Okay, let's talk about types. What's the type of variables whose value is undefined or null? Do they even have a type? Can we assign undefined or null to a variable of string or number or boolean type?

In JavaScript, we can get the runtime type of a variable by using the typeof keyword. Depending on the actual value of the variable, we will receive one of the following strings back:

  • "string"
  • "number"
  • "boolean"
  • "bigint"
  • "symbol"
  • "object"
  • "function"
  • "undefined"

As you can see, "undefined" is one of the ECMAScript types. And as you can guess, we get this value when we try to use typeof with a variable whose value is undefined (i.e. it hasn't been set yet, or has been unset explicitly by setting it to undefined).

let foo;
console.log(typeof foo); // "undefined"
let bar = 42;
bar = undefined;
console.log(typeof bar); // "undefined"
Enter fullscreen mode Exit fullscreen mode

So far so good. But what about null? Why isn't there a "null" type. What happens if we use typeof with a variable whose value is null? The answer may surprise you.

const foo = null;
console.log(typeof foo); // "object"
Enter fullscreen mode Exit fullscreen mode

Wait, but doesn't type "object" imply that the variable is an object? Well yes, but actually no. As it turns out, in JavaScript, the type of null variables is also "object". This is a bug since the early days of JavaScript, and unfortunately can't be fixed. You can read more about it here.


Wait, isn't there a null type in TypeScript?

That's correct. TypeScript does have a null type. However, this is a compile-time type, used for compile-time type-checking. At runtime however, when the TypeScript code has been compiled to JavaScript, there is no null type. Instead, you only have the aforementioned runtime types.

Does this mean that we can't assign null to variables whose compile-time type is something else, like string or boolean or number? What if we have a function that accepts a parameter of type string, but we pass it an argument of type null instead?

function debug(message: string) {
  if (message) {
    console.debug(message);
  }
}

debug("Testing debug log...");
debug(null); // Does this work?
Enter fullscreen mode Exit fullscreen mode

The answer is, it depends. More specifically, it depends on whether or not we have strict mode turned on. Even more specifically, it depends on if we have set strictNullChecks to true in our compiler options.

If we have strictNullChecks set to false, the TypeScript compiler will happily compile this code to JavaScript. But if we have strictNullChecks set to true, compilation will fail with the following error:

Argument of type 'null' is not assignable to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

Note that if this compiler option is turned off, apart from null, it also allows us to assign undefined to variables of other types.

If we do have strict mode turned on like a good programmer, and we don't know if a variable can be null at runtime, we can use union types to allow the null type too.

function debug(message: string | null) {
  if (message) {
    console.debug(message);
  }
}

debug("Testing debug log...");
debug(null); // No problem!
Enter fullscreen mode Exit fullscreen mode

How do I check if a variable is undefined or null?

This is a very common use case, and one where many programmers make mistakes. Often we want to add defensive checks inside functions which check if the argument passed is defined. Maybe we want to handle null arguments separately. How should we check if the argument is in fact undefined or null?

In JavaScript, to check if a variable is undefined, we can do one of two things:

let foo, bar;
if (foo === undefined) {
  console.log("foo is undefined"); // "foo is undefined"
}
if (typeof bar === "undefined") {
  console.log("bar is undefined"); // "bar is undefined"
}
Enter fullscreen mode Exit fullscreen mode

We can either check if the value of the variable is undefined, or check that its type is "undefined". These two are equivalent, because undefined is the only value which has a runtime type of "undefined".

Unfortunately, this same tactic of checking the type will not work with null, because its type is "object", which is not unique to null. So to check if a variable is null, we must explicitly check its value.

let foo = null;
if (foo === null) {
  console.log("foo is null"); // "foo is null"
}
Enter fullscreen mode Exit fullscreen mode

What about TypeScript though? TypeScript has a null type right?

Yes, but as mentioned above, it's only a compile-time type. In most cases, we don't even need to add such defensive checks in TypeScript, since the compile-time type checking does this for us if we have strict mode on. We won't be able to pass arguments of type null or undefined if the corresponding parameter type is something else.

However, there are cases when we aren't sure what type of argument we might receive (for example, if we are dealing with something that has been sent over the wire), and we do want to add runtime defensive checks. In that case, we can include the same checks as above, which will be included in the compiled JavaScript, and execute at runtime.

Top comments (0)