Why Null Matters More Than You Think

In this article, I'm sharing insights from a TypeScript-centric perspective. For those still entrenched in JavaScript. Are you even in 2024? Although the principles in this guide are not only applicable to TypeScript/JavaScript, they are conventions for creating more robust and maintainable code across frameworks and languages.

Having worked with TypeScript for many years now, I've grown increasingly attentive to the nuances that impact code quality. A common teaching when you first learn JavaScript/TypeScript from your Guru of choice: is to represent empty values with empty strings. Many developers, including those I've collaborated with, adhere to this practice.

I believe this flawed approach can lead to unpredictable and non-deterministic behaviour in your code. This is where the proper use of null and undefined becomes crucial.

Consider the following example, where we have a function designed to retrieve an avatar. If a name is provided, the function uses DiceBear (kudos to @Friosn for making me aware of this library) to generate an SVG string representation. However, the focal point of this discussion is the function's second return statement. At first glance, it might seem logical to return an empty string when no avatar is available, but that's not the ideal approach.


import { lorelei } from '@dicebear/collection';

function getAvatar(name: string | null) : string {
    if (name) {
        const avatar = createAvatar(lorelei, { seed: name });
        return avatar.toString();
    }

    return "";
}
A more appropriate return type in this scenario is null. The revised function would look like this:

import { lorelei } from '@dicebear/collection';

function getAvatar(name: string | null) : string | null {
    if (name) {
        const avatar = createAvatar(lorelei, { seed: name });
        return avatar.toString();
    }

    return null;
}

By returning null, we're explicitly indicating, "We attempted to return an avatar, but since it's unavailable, we're returning a representation of 'no value'."

To Null, or not to Null

This distinction is crucial for several reasons:

  1. Semantic Clarity: Clarity in coding is paramount. An empty string ("") and null convey distinct meanings. An empty string is a type of string, albeit with no characters, whereas null represents the complete absence of a value. In the getAvatar function, returning null clearly states the unavailability of an avatar, avoiding confusion with a potentially valid but empty avatar.
  2. Type Safety: TypeScript's strength lies in its type safety. Allowing null as a return type enables stricter type checking, reducing the likelihood of bugs where an empty string is incorrectly used instead of a legitimate string value. This clarity benefits both the compiler and other developers who read your code.
  3. Error Handling and Debugging: Error handling and debugging become more straightforward using null. It indicates a value's absence, making it easier to implement specific error-handling routines or identify issues during debugging. Conversely, an empty string might not immediately signal an error state, leading to more elusive bugs.
  4. API Design and Expectations: Clear expectations are crucial when designing APIs. By using null to indicate no value, you set a clear expectation for API users: they should anticipate either a valid string or a null value, not a pseudo-valid empty string. This adds an extra layer of validity to your code, leveraging types effectively.

Consider variables as glasses: null is akin to an empty glass, indicating its emptiness. An empty string, however, is like a glass labelled String but empty inside, leading to a mismatch between type representation and actual content, adversely affecting code behaviour.

It's like going to a cafe and asking for a glass of lemonade, only to be given an empty glass and told, "Here is your glass of lemonade." The implementation of the first version of getAvatar is similar to this scenario. With the signature getAvatar(name: string): string, there is no option to handle cases where no lemonade is found. Instead, you ask for lemonade and receive an empty glass.

You ask for lemonade and receive an empty glass.

Null/Empty String being represented as glasses.



A Note on Undefined

Regarding undefined, i signifies the complete absence of a value. As a rule of thumb, avoid returning undefined explicitly. undefined should be reserved for situations where a reference to a value doesn't exist. For instance:


const myVar = undefined;

Here, null signifies an empty value, while undefined means non-existence. To draw an analogy, consider undefined as not having a glass, whereas null represents an empty glass.

Undefined/Null as non-existent and empty glasses.

By implementing these subtle yet impactful changes, you can enhance the maintainability of your code with stricter type definitions, achieving cleaner and more deterministic code.

Back to blog

Leave a comment