loading...
Cover image for How do you deal with null vs undefined?

How do you deal with null vs undefined?

johannesjo profile image Johannes Millan ・1 min read

I just recently started refactoring my large scale angular app to Typescript's strict mode. Dealing with lots of null checks the question arose again if I should prefer using undefined over null or vice versa. A couple of thoughts:

  1. Is using undefined for initially undefined values and using null whenever you want to unassign a value a good option?
  2. Using undefined everywhere simplifies things at first, but then there is JSON (only null available) and API responses. How to best deal with those?
  3. How to best deal with pre-checks for when you are reasonably confident that a value is not null, but it theoretically could be (e.g. angular @Inputs you always assign)? Do you prefer to typecast or do you use error checks?

I wonder how do you deal with the problem. Do you prefer one over the other? Do you use both? What's your take on it?

Posted on by:

Discussion

markdown guide
 

I think the complexity people see comes from a lose definition of those two (undefined and null). They do not represent the same thing. An empty array is different from an undefined array, and it is also different from an array with a null value in one position. In those cases, those definitions get more clear. And I think the best approach is just to use what the values mean.

So, I would use undefined whenever a variable has not been assigned yet, or when a method or function does not return anything (exactly how the language behaves on runtime).

On the other hand, null is used when you explicitly say that variable points to no value. You know that variable exists, but you have nothing to assign to it.

I would say undefined is more a "source code" thing, and null more of a runtime thing.

Angular manages state in a very complex and imperative way, in my opinion, which makes checking values harder. But I would use undefined for undefined values and null when I've passed the point where I would assign a value and I had nothing. If it is a collection, I would just initialize it without values.

But I don't think there is a silver bullet answer. Each case should be dealt diferently.

 

Thank you very much! That's an excellent answer and I agree pretty much with everything you say.

I think much of my particular problems are caused by my data being saved to a document oriented schemaless database (first localStorage and now IndexedDB) and the process of it evolving over time made me lose confidence what's actually in there. Probably makes most sense to solve this problem first and then to revisit the null/undefined problem as you mention.

 

Adding to this answer, in JS/TS if you have to initialize a collection with empty object, it is often too early. Most JS runtime internally implement different optimization based on kind of the object. If you create an empty array, and later populated it with elements, you get a less optimized array than if you populate it on initialization. Refer to V8 blog for more details.

 

I consider the difference to be one of implicitly nonexistent (undefined) vs. explicitly having no value (null). A newly declared variable that has no initial value, vs. an array element that you've cleared, is a suitably illustrative example of the difference.

As for handling them, I consider casting without checks a strict no-no in strongly typed languages like TypeScript. Don't blindly cast... but DO check to make sure the operation you're attempting is valid if you can, and trap the error if you can't.

 

I consider casting without checks a strict no-no in strongly typed languages like TypeScript

That's an excellent point! Thank you very much! How do you usually deal with (unexpectedly) failing type checks in these scenarios? Do you just throw an error?

 

Yep. I error (or skip if optional).

 

I just use undefined for everything unless some API forces me to use null. Having to decide when to use one or the other is just an unnecessary complexity. 90% of the people I work with struggle to have the time to concentrate on refactoring their code to the point where it is clean, I think it's too pedantic to make them think about something as minor as this on top of that.

 

There was some brief conversation on this very topic as part of a broader discussion with @addyosmani on a recent DevDiscuss episode...

play pause DevDiscuss

Might help add a tiny bit of color to the conversation.

 

I just use undefined everywhere, and ruthlessly convert nulls to undefined as they come off an API.

I appreciate the difference. I'd sum it up as a nullable type being shorthand for a set with either zero or one item. null is just how we say the set has no entries, so null, in my head, has a type and is always used in the context of a nullable type. undefined never has a type, it's just a void.

But, I'm really not a fan of this kind of unneeded technical nuance. There are few cases IME where converting all the nulls to undefineds has broken my code, but having a source of undefineds sneak into a system I'd designed exclusively for nulls sounds like a day I'll never get back.

I get why someone would prefer to use null how it was designed, but I already make complex enough code, KISS has to be my mantra for this sort of thing.

 

You don't deal with null vs undefined but you use it as one thing. The key is to use the concept of nullish. My few cents here - Maybe Just Nullable

 

When we write TypeScript apps, we tend to prefer using neither null nor undefined. We use null objects : let’s say you have a Customer to display. We use a const NoCustomer of same type to underline that there is no customer selected/in state at the moment. Leverage the power of typed languages ;)

Now you can say if(customer != NoCustomer) explicit, no doubt possible.

 

In JS I don't think in terms of null or undefined, but think in terms of "nil".
I use a utility func similar to Ramda's R.isNil.

And I have a complement func called, isNotNil

Alt Text

Source Code:

gist.github.com/funfunction/0e64a5...

 

A simple rule that I introduced in the previous company where I worked:
Always initialize variables with null, any encountered undefined was treated as an error.
Typically, we'd only have undefined values when working with externally provided data, and we'd have validation logic on incoming data anyway, which took care of transforming it into null (or throwing an error, depending on which was appropriate).

Simply put, a null is intentionally empty, an undefined indicated that something wasn't 100% right.

 

I use undefined is a abort in binding, for example in Web Atoms framework, we can create binding like

    <div text={Bind.oneWay(()  => this.a ? this.b : this.c )}/>

In above case, this basically refreshes text whenever a b or c is modified, however, I can set any of it to undefined to cancel the update operation. Update will occur only if all are not undefined, this allow me to clearly distinguish between undefined and null.

 

I guess the biggest consideration is this:

// a React example, but simple enough :)
function ShowProgress({ data = loadingStateData }) {
    if (data == null) {
        return <LoadingFailed />
    }
    return <Component data={data} />
}

Whether actually doing logic like this is a good idea or not is up to the reader (one might argue it is "confusing"), but default values is the biggest gotcha with undefined since defaults fill always override it when passing to functions or when destructuring. In those cases null will stay.

So essentially every time you want to indicate you couldn't get a value it is safer to indicate that as null, because undefined may be overridden by a default value. Personally I like to deal with them as equal by using == null comparison. And I dislike applying strict typing to JS so I don't need to waste time telling computer what things are. Instead I force type to whatever is needed, such as using ~~variable to force variable as integer Number that is never NaN, or check that the type matches expectation. This has the benefit of working in runtime as well (unlike TypeScript which doesn't help you with unexpected runtime issues).

 

Simple answer: Be consistent with their use in your code.
There are subtle differences, were one can have variable behavior from the other. However, in most cases it is personal preference.

 

I think null is for explicitly acknowledging that this has no value and if it's undefined it's a better indication of a bug / error / unhandled case.