> mthadley_

Undefined in TypeScript

Every programming language has the concept of “no value”. Many follow the example of C and its NULL; Ruby has nil, Python None. More recent and functional languages like Rust or Elm use a union type, like Option<T> or Maybe a, with a union member representing “nothing”, but lets ignore those for now.

JavaScript is different in that it has two primitive types to represent the absence of a value, null and undefined. I’m personally not aware of another language that does this.

Undefined

Most of us programmers have written at least some JavaScript by now, and probably first encounter undefined with the infamous “undefined is not a function”. This happens because JavaScript runtimes use undefined as the “uninitialized” value; either a variable without an assignment, or a non-existent field of an object. The latter often being a typo or type-mismatch.

undefined is built into the semantics of the language. A bare return statement with no value causes a function to return undefined. A function with no return at all also returns undefined. A function with an optional parameter has its value initialized as undefined when no argument is given.

Null

On the other hand, null does not “naturally” occur through the usage of language constructs. A programmer needs to explicitly initialize, pass, or return a value of null. It’s much more intentional, and meant to represent the explicit absence of data.

This is in contrast to undefined, which is often more like “Oops, you are trying to access something that doesn’t exist” by mistake.

Inconsistency

Consider methods meant to retrieve data. Your “fetchers” and “getters” of the world. From my description of the undefined and null, you might expect that these all prefer null. Here are some common browser APIs:

API null undefined
Array.prototype.find()  
Cache.match()  
Document.querySelector()  
FormData.get()  
Headers.get()  
Map.get()  
Storage.getItem()  
URLSearchParams.get()  

Yeah, they aren’t consistent at all. My random sampling leans towards null, but barely so. Maybe Map.get() is trying to be consistent with [] access of an object when the key doesn’t exist, but then why does FormData return null for the same case?

TypeScript

When you start writing TypeScript, you run headlong into this inconsistency. If you don’t give it much thought, you end up with long union types everywhere like string | null | undefined as the results of various operations get smashed together.

You might react to this with a “pick one” policy, with most folks picking undefined, since it shows up naturally from basic language semantics. Just don’t use null whenever possible. In some ways, this mirrors the other languages like Ruby, with undefined playing the role of nil.

However, I’ve seen this approach backfire. I’ve used a popular ORM where a field with a value of undefined meant “don’t update this field in the resulting SQL”, while null meant “set the underlying column value to SQL NULL”. This lead to subtle bugs where data was not being cleared, since the rest of the codebase was trying to prefer undefined.

Why Not Both?

Ruby is a language I love and so in general I enjoy the simplicity of a single null-like value, nil. But surprisingly there are cases where using nil ends up being lossy.

If you define a method that takes an optional parameter, and the caller passes no value, you’ll generally assign it the default of nil:

def foo(a, b, options = nil)
  # Do your foo thing
end

But what if the caller explicitly passes the optional parameter as nil? It may not matter for your particular method, but certainly sometimes it does. See how Rails uses object singletons to detect this.

So maybe the concept of undefined has its place, and we should use it alongside null judiciously.