When I first started learning JavaScript and TypeScript one of the parts that I struggled with was the difference between undefined and null. I even tweeted Brendan Eich once to ask if he regretted the distinction and he seemed to acknowledge that he did.

Let me show you a problem that new TypeScript developers quickly run into with this.

Imagine we have user objects with a name property that can be null.

user = { name: null }

We may need a sayHello() function to print a custom greeting. The name attribute “feels” optional so we might declare name as an optional parameter like this:

function sayHello(name?: string) {
    if (name) {
        console.log(`Hello ${name}!`)
    } else {
        console.log('Hello you!')
    }       
}

But when we call it we get a compiler error: Argument of type 'null' is not assignable to parameter of type 'string | undefined'

user = { name: null }    
sayHello(user.name) // XXX Compiler Error: Argument of type 'null' is not assignable to parameter of type 'string | undefined'

The problem is that an optional string allows a string or undefined but we are passing null which is different. undefined is more like omitted / “no value present” while null is more like a special value that indicates “no object”. Confusing I know. Here are a few ways we might fix this:

1) Convert null to undefined:

const user = { name: null }
sayHello(user.name || undefined)

2) Update the function signature to allow null:

function sayHello(name?: string | null) {
//...
}
const user = { name: null }
sayHello(user.name)

3) Allow null but disallow undefined by making it non-optional:

function sayHello(name: string | null) {
//...
}
const user = { name: null }
sayHello(user.name)

4) Allow null, disallow undefined, and allow callers to omit it by setting a default value (which makes it effectively optional):

function sayHello(name: string | null = null) {
//...
}
const user = { name: null }
sayHello(user.name)
sayHello() // parameter can be omitted

So what is the best answer? Like most things I think the answer is “it depends”. My best advice is to try to come to terms with the fact that null and undefined values are just different things in JavaScript and to consider them independently. Here is how I think about it:

  • Treat undefined as “uninitialized” (you didn’t assign a value / programming error) or “no value present” (the object doesn’t have that property).
  • Try not to use the undefined global directly.
  • Treat null as an initialized value that was intentionally set to “no object”.
  • undefined and null will often be treated the same but some APIs can have different semantics. An ORM update() function for example may take a set of name/value pairs to update. A null value may indicate that a value should be set to null. An undefined value may indicate the value should not be updated.
  • If an argument isn’t needed at every call site I’ll make it optional OR give it a default value if there is one that makes sense.
  • If an argument should accept null then I’ll allow null by adding | null to its type.

What did I miss? Please let me know how you think about it.