JavaScript Nulls and Programming Minutiae

Painting “The Death of Socrates” by Jacques-Louis David
“The Death of Socrates” by Jacques-Louis David

Socrates once said “the unexamined life is not worth living.” He was immediately sentenced to death afterwards.

I, too, often find myself examining the minutiae of programming languages. Thankfully, I have not been put to death for it (yet).

After spending more than a decade honing my Android development skills, I’ve recently switched back to my first foray into professional development, JavaScript[1]. It has a lot to examine, and much like Socrates, I have many, many questions.

Today, let’s look at a seemingly simple question: how do I represent absent data in function returns?

Suppose I've got a function that queries the database for Foo:

async function getFoo(id: string): Foo | ??? {
  const rows = await query(/* ...some SQL... */)
  if (rows.length > 0) return rows[0]
  else return ???

When Foo is not found, what do I return?

For whatever reason, JavaScript provides two options here: null and undefined[2]. There are subtle distinctions between them, but fundamentally they both represent the absence of data.

Which should I use? Let’s try to answer this question without drinking hemlock.

So What?

Before we begin, I’d like to answer a more fundamental question: who cares?

On its face, this problem feels pretty dumb. null and undefined both mean approximately the same thing, just return whatever you feel like, right? It doesn't really matter, so you can leverage the vibes instead of your brain.

These sorts of choices come up everywhere: in languages, frameworks, APIs, or even your own codebase. Situations where there’s more than one way to do something, but it’s not always clear which way is better because the options are so similar.

Should I concatenate strings using + or string templating? Should I use a for-loop or a forEach() iterator? Should I use tabs or spaces?

Here’s why I give a hoot:

First, when you study a seemingly arbitrary choice, you sometimes come out with some real enlightenment. Perhaps the choice seemed arbitrary at first, but through careful study, you realize there are compelling reasons to use one method or another. For example, I investigated properties vs. functions in Kotlin and came out with a deeper understanding of val. My initial (naive) understanding equated val with “immutable” when it actually just means “read only”, and all my future work with class properties was more nuanced because of my research.

Second, the more ambiguous a choice is, the longer people spend time on it, so it’s best to reduce the ambiguity. If it were obvious what the right answer was, everyone would do the right thing and not spend any further time thinking about it. But when there’s no obviously correct answer, you have to stop and think. And when discussing ambiguous choices, oftimes all you’ve got are personal opinions, and opinion-based arguments are pointless and infinite (see: the never-ending discussion of tabs vs. spaces[3]). If we can come up with a well-reasoned nudge in one direction, we can spend less time stressing about it going forward.

Lastly, sometimes these questions just drill their way into my head and I can’t get them out until I have an answer. That’s a personal problem and applies directly to the null vs. undefined dilemma (which, believe it or not, I've been ruminating upon for years).

Alright, Let’s Debate

Let’s get back to the question at hand: coming to a conclusion on using null vs. undefined (hopefully without getting canceled on Hacker news for having imperfect thoughts).

My first angle of inquiry: is it possible to write a codebase that only uses null or undefined? That would be awfully convenient (which is why almost every other programming language only has one null!). Unfortunately, a fellow sophist already dug into this idea and came up with the answer “no”, at least for most codebases. I’ll summarize the argument here:

  • It’s easy to rule out a codebase that only uses null because it is impossible to get away from undefined. It is the value that properties start with before initialization, and it’s returned by many core JavaScript functions (e.g. Array.find()).

  • It is possible to only use undefined but only in narrow circumstances. For example, TypeScript’s codebase has no nulls. However, if you use JSON or depend on 3rd party libraries you open yourself up to the possibility of using nulls (since both can give you nulls anytime).

It’s worth pursuing an undefined-only codebase, but may be impossible for you. For my own use case, I will have to deal with nulls, so I must scratch this utopian idea.

Can we answer this question by shifting our perspective to that of the consumer? How will they react to receiving a null vs. undefined?

The annoying thing about being a consumer is you won’t always know if you’re getting back null or undefined, so you have to handle both cases[4].

How do we if-check “not null and not undefined”? Here's a bunch of ways, most with drawbacks:

  • if (myVar !== undefined && myVar !== null) {} is too verbose.
  • if (myVar) {} coerces extra values (“” and 0 are also true).
  • if (myVar != null) {} breaks the guideline against using !=
  • if (!isNil(myVar)) {} (via lodash) works!

Of all these options, if (!isNil(myVar)) works best. It’s succinct and type inference still works. And if you’re allergic to dependencies, you can write your own isNil().

Because consumers have to be defensive, it doesn’t matter whether I return null or undefined. That said, I find it revolting that typeof null === ‘object’, so my preference is to return undefined. Plus, that choice helps out anyone who might want to eventually try to go for an undefined-only codebase.


My personal conclusion:

  • Represent absent data with undefined.
  • Use isNil(xyz) for null checks.

I don’t feel particularly warm and fuzzy about my conclusions, but I suppose that’s just how it goes when you’re working with JavaScript.

Screencap from the movie Chinatown, from the ending; a character is saying “Forget it, Jake. It’s JavaScript.”

  1. Technically, TypeScript; but because TypeScript is built on top of the bog that is JavaScript, you are constantly having to sink to JavaScript’s level. ↩︎

  2. TypeScript also includes other null-adjacent oddities: never, void, and unknown. I don’t consider these reasonable options for this situation, and they don’t actually exist in JavaScript, so I’m ignoring them. ↩︎

  3. I know people present logical arguments for tabs vs. spaces, but the fact that this matter remains unsettled after decades of arguments seems like proof enough to me that there’s no one correct answer. ↩︎

  4. If you enable TypeScript’s strictNullChecks, then you have to define whether a function returns null or undefined, which can help here; again, though, JSON/3rd party libraries may not be as helpful here. ↩︎