Leaving null, Meeting undefined

2021. 10. 5.

Leaving null, Meeting undefined
image by Bongkarn Thanyakij

Recently, during a code review, a discussion arose about the use of undefined. Personally, for a very long time, I had quite clearly distinguished between null and undefined and used them according to their respective purposes, so I hadn't really thought about looking into this further. Ironically, the issue that sparked the discussion turned out to be a misunderstanding about something else and was easily resolved. However, while searching related topics, I encountered something unexpected: the argument that we should avoid using null and stick solely to undefined. This was even stated in the TypeScript coding conventions: "Use undefined, do not use null." I think I saw this before but just brushed it off, as my preconceived notions were too strong. Luckily, the new book by Douglas Crockford that I happened to be reading also touched upon this topic, allowing me to ponder it a bit more. However, the book only covered it lightly, which was a bit disappointing.

It seems that even things that seem obvious and unchanging can see their paradigms shift over time. Nothing stays the same.

Consequently, I'm considering not using null directly in the codebase for future projects and relying solely on undefined. Especially in toy projects, I plan to avoid null for the time being, until I arrive at my own conclusion based on experience.

Fixed Ideas

There are a few characteristics that come to mind when thinking about undefined.

  1. Accessing a non-existent property on an object yields undefined.
  2. A variable not initialized with a value holds undefined.
  3. undefined represents a value that does not yet exist (has not been initialized).
  4. Therefore, assigning undefined directly creates a contradiction. It means assigning a value representing a "not yet existing" state; the moment you assign it, the value exists, yet its value (undefined) signifies non-existence.
  5. Some older browsers didn't treat undefined as a keyword, allowing it to be used like a variable, potentially resulting in it not being the pure undefined value.

There are more, but I've listed the features relevant to distinguishing between undefined and null.

My fixed ideas might have formed from what I've seen and felt throughout my career in this field. I took it for granted that undefined, being a sort of pre-definition state, meant it was natural to use null, which carries the clear meaning of an "empty value". I don't even know why I started thinking this way. I can't remember where I read about it, or even if I ever saw a precise definition like this. Anyway, it was firmly fixed in my mind.

  • undefined means a value not yet initialized, non-existent.
  • null means an empty value, but it exists (like an empty string).

I found an image online that properly explains my understanding.

Truthfully, while I used null distinctively, it wasn't without drawbacks. Sometimes, because I used null, I had to check for both undefined and null. null having a typeof of object can also lead to mistakes. While null is shorter to type than undefined, avoiding the default undefined value meant the code sometimes became longer.

let value;

// vs

let value = null;

Still, I never considered using undefined instead of null, because I thought they had different purposes. In other words, I never thought about not using null in my code, because it had its purpose.

JavaScript Has Two?

Most programming languages have a value or type to represent absence. Usually, there's just one. Languages like Java, C, C++, C# use null. (C also uses \0, but it essentially means the same as null.) Lisp, Objective-C, Swift use nil. There are many other languages, but I've only considered those I know a little about. Yes, anyway, they all have one.

There are cases like Rust, which doesn't have null. Haskell doesn't either. It seems languages applying functional programming principles tend to avoid null. Given the nature of functional languages, avoiding null makes sense. Emphasizing immutability, they wouldn't want to use values that could potentially change later. It's either something exists or it doesn't. (Though I don't know much about functional programming...)

Languages that utilize absence mostly use a single value and don't distinguish between null and undefined. I hadn't really recognized this fact until now. "Ah, JavaScript has two?" I never thought of that as a problem. Having more than one value representing a negative state, bundled together as Truthy and Falsy values through implicit type coercion, offered both convenience and ambiguity simultaneously. It was a double-edged sword that put pressure on you to use it "well" or face the consequences. Perhaps I'm overthinking the cause.

Reasons to Use Only undefined

So why should we only use undefined? Is null better than undefined? The argument for using only undefined is essentially an argument for choosing one over the other. Thus, the question becomes which one is better to use.

null is used intentionally by the developer. In contrast, undefined is used by the JavaScript engine.

let something;

When declaring a variable like this without an initial value, undefined is assigned to the something variable. This happens even if the developer doesn't explicitly assign it. Rather than saying the value was implicitly assigned in the code, it's more accurate to describe it as the result of the JavaScript engine's internal process: before executing this code, a new execution context's activation object (AO) is created, variables within the function are collected, and they hold the undefined value until explicitly assigned a value in the code. Even if the code declares and initializes the variable simultaneously, the process involves the variable first holding undefined before its value is changed. Anyway, undefined is invariably used initially.

The engine uses undefined, not null. At least in this process, there's no way to make the engine use null instead of undefined. undefined is the default and irreplaceable value. Similarly, when a function doesn't explicitly return a value, it returns undefined.

Here lies the first reason:

"The engine uses undefined, not null."

If a developer intentionally uses null, situations requiring checks for falsy values necessitate explicitly checking for both undefined and null. For example, for a number variable, one might need to check for the number 0, null, and undefined.

Second reason:

"Using null adds another falsy value."

And finally, there's the third reason, a well-known one:

"Using the typeof keyword identifies the type of null as object."

This is a clear bug in JavaScript, not intended by the specification. Despite being a bug, it cannot be changed because it has been used as if it were standard for so long that altering it would significantly impact existing code. Thus, it's reluctantly left as is. To fix this issue, we might need another type-checking keyword alongside typeof. In other words, it's unlikely to be resolved.

If you use null to represent the absence of an object in a variable meant to hold an object, you need to be extra careful when using typeof for type checks. It's quite ironic that the code intended for extra caution (typeof checks) requires even more caution and attention when used. This problem doesn't exist if null is not used.

Problems When Using Only undefined

The first problem relates to undefined itself. Despite being a keyword, undefined was not always treated as such and could be used as an identifier.

function doSomething1(undefined) {
  console.log(undefined + 1);
}


function doSomething2() {
  var undefined = 0;
  console.log(undefined + 1);
}

Modern browsers have resolved this issue, preventing assignment to undefined. However, if you need to support older browsers, there's still potential for problems. Even developers unaware of this issue are unlikely to introduce such nonsensical code into the codebase thanks to static analysis tools like ESLint, but there's no way to prevent malicious manipulation from external sources at runtime.

The second problem is that the DOM API uses null. It's less of a problem and more of a "Huh? We came this far arguing the engine uses undefined, but the DOM API uses null?" situation. Of course, the engine and the DOM API are separate entities, but the question "Why?" does arise. APIs like querySelector or getElementById, which search for elements in the DOM, return null when the target is not found.

The third problem is that JSON doesn't support undefined. Generally, null is used in JSON to represent optional values that are absent. undefined is not a valid value according to the JSON specification. One might think, "Why fuss over JSON?" but it's used in our code anyway. We've come this far trying to eliminate the nagging ambiguity; there shouldn't be exceptions considered from the start.

Using Only undefined

There were three potential problems with using only undefined:

  1. The purity of undefined.
  2. The DOM API's use of null.
  3. null in JSON.

Let's consider alternatives for each problem.

Purity of undefined

As mentioned earlier, this issue tends to disappear over time. In modern browsers, undefined is purely guaranteed to be undefined. Even I, having developed since the early IE6 days, haven't experienced problems caused by the value of undefined being changed. Malicious intent could cause issues, but it was always just a possibility.

The most ideal solution is to raise the browser support baseline to eliminate this problem. It's a bit tricky to specify the exact browser range, but mostに対応 versions before ES6 support were likely patched. What's certain is that browsers supporting ES6 don't have this issue. Nevertheless, if restricting browser support isn't an option or if you want to be extra cautious, create and use a pure undefined value.

const undef = (() => {})();

Leveraging the side effect that a function returns undefined when it returns nothing, we can create a pure undefined value. There's no purer undefined than this :)

DOM API's null Usage

This part is a bit annoying. At this point, frustration arises, questioning why these APIs had to use null. For instance, the relatively recent WeakRef's deref() method, which checks if the referenced object exists, returns undefined when the object doesn't exist. It doesn't use null. So why did the DOM APIs insist on using it? Complaining won't change anything. Anyway, they use null. One might wish things had changed, at least with the querySelector API, but then again, that would create its own kind of terrible inconsistency.

So, we inevitably have to perform the task of converting null to undefined each time.

 const element = document.querySelector('#something') || undefined;

This isn't much of an alternative, but nowadays, using frameworks like React or Vue is commonplace, reducing the need for direct DOM API access. It might only be slightly bothersome at the library or general module level.

null in JSON

Typically, APIs are designed to use null to represent the absence of optional data. While the JSON spec dictates this, it's often better not to send the value at all if it's missing. It is literally "absent". Instead of explicitly using a property with a key and null to represent an absent value, omitting the property entirely has more advantages. This is a clear benefit; at the very least, it reduces the payload size.

const badJsonPayload = {
 name: 'shiren',
 ownedHouse: null // Represents absence with null
}

const goodJsonPayload = {
 name: 'shiren' // Property is simply omitted
}

And anyway, I don't actually own a house. ㅜㅜ

Conclusion

While learning new technologies is important, it seems equally important to strive for deeper and richer thinking by accepting different opinions on things considered established and looking at them from new perspectives. Achieving such flexibility is truly difficult. Towards the end of Douglas Crockford's new book, there's a line: "Science advances one funeral at a time." It means that because people's ability to accept and adopt new paradigms lags behind the speed at which these paradigms emerge and change, progress often requires a generational shift—people passing away. Personally, this was the most impactful(?) part of the book for me. Accumulating unchanging, fixed ideas is probably how one becomes resistant to change (a kkondae). It seems the only unchanging thing in this field is the fact that nothing is unchanging.

♥ Support writer ♥
with kakaopay

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

shiren • © 2025Sungho Kim