Write more readable code with TypeScript 4.4

Written by John Reilly ✏️

An exciting feature is shipping with TypeScript 4.4. It's called control flow analysis of aliased conditions, which is quite a mouthful.

In this post, we'll unpack this new feature and demonstrate how it contributes to improving the readability of code.

Indirect type narrowing via const

On 24 June 2021, Anders Hejlsberg closed an issue on the TypeScript GitHub repository with the title "Indirect type narrowing via const." The issue had been open since 2016 and it was closed because it was covered by a pull request addressing control flow analysis of aliased conditional expressions and discriminants.

It's fair to say the TypeScript community was very excited about this, judging from both reactions on the issue:

Not to mention the general delight on Twitter:

Zeh's tweet sums up the significance of control flow analysis of aliased conditions quite nicely:

Lack of type narrowing with consts made me repeat code, or avoid helpfully namef consts, too many times

With this feature, we can write more readable code with less repetition. That's amazing!

Writing more expressive code with TypeScript 4.4

Rather than diving straight into an explanation of what this new language feature is, let's start instead from the position of writing some code and see what's possible with TypeScript 4.4 that we couldn't tackle previously.

Here's a simple function that adds all the parameters it receives and returns the total. It's a tolerant function and allows you to supply numbers in the form of strings as well. So it would successfully process '2' as it would 2.

This is, of course, a slightly contrived example, but it's a good way to demonstrate the new feature:

function add(...thingsToAdd: (string | number)[]): number {
    let total = 0;
    for (const thingToAdd of thingsToAdd) {
        if (typeof thingToAdd === 'string') {
            total += Number(thingToAdd);
        } else {
            total += thingToAdd;
        }
    }
    return total;
}

console.log(add(1, '7', '3', 9))

Try it out in the TypeScript playground.

This function, although it works, is not super-expressive. The typeof thingToAdd === 'string' serves two purposes:

  1. It narrows the type from string | number to string
  2. It branches the logic, such that the string can be coerced into a number and added to the total

You can infer this from reading the code. However, what if we were to rewrite it to capture intent?

Let's try creating a shouldCoerceToNumber constant that expresses the action we need to take:

function add(...thingsToAdd: (string | number)[]): number {
    let total = 0;
    for (const thingToAdd of thingsToAdd) {
        const shouldCoerceToNumber = typeof thingToAdd === 'string';
        if (shouldCoerceToNumber) {
            total += Number(thingToAdd);
        } else {
            total += thingToAdd;
        }
    }
    return total;
}

console.log(add(1, '7', '3', 9))

Try it out in the TypeScript playground.

This is valid code. However, TypeScript 4.3 is choking with an error:

The error being surfaced is:

Operator '+=' cannot be applied to types 'number' and 'string | number'.(2365)

What's happening here? TypeScript does not remember that shouldCoerceToNumber represents a type narrowing of thingToAdd from string | number to string. So the type of thingToAdd remains unchanged from string | number when we write code that depends upon it.

This has terrible consequences. It means we can't write this more expressive code that we're interested in and would be better for maintainers of our codebase. This is what TypeScript 4.4, with our new feature, unlocks.

Let's change the playground to use TypeScript 4.4 instead:

Try it out in the TypeScript playground.

Now that we've made the switch to TypeScript 4.4, delightfully, we no longer have errors. And as the screenshot shows, the thingToAdd variable has been narrowed to a string. This is because control flow analysis of aliased conditions is now in play.

We're now writing more expressive code and TypeScript is willing us on our way.

Read more

This feature is a tremendous addition to the TypeScript language. It should have a significant, long-term, positive impact on how people write code with TypeScript.

To read more, check out the excellent TypeScript 4.4 beta release notes. There are also some other exciting features shipping with this release as well.

Thanks very much to the TypeScript team for once again improving the language and making a real contribution to people being able to write readable code.

25