When to use null vs undefined with optional parameters in TypeScript
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
andnull
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. Anull
value may indicate that a value should be set tonull
. Anundefined
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 allownull
by adding| null
to its type.
What did I miss? Please let me know how you think about it.