TypeScript 5.5: A Blockbuster Release

We TypeScript developers are a lucky bunch. While some languages (Python, JavaScript) are released annually, every three years (C++) or even less, we get four new versions of TypeScript every year. TypeScript 5.5 was released on June 20th, 2024, and it was a real blockbuster. Let's take a look.

TypeScript's motto is "JavaScript with syntax for types." New versions of TypeScript don't add new runtime features (that's JavaScript's responsibility). Rather, they make changes within the type system. These tend to take a few forms:

  1. New ways of expressing and relating types (e.g., template literal types in TypeScript 4.1)
  2. Increased precision in type checking and inference
  3. Improvements to language services (e.g., a new quick fix)
  4. Support for new ECMAScript standards
  5. Performance improvements.

TypeScript 5.5 doesn't introduce any new type syntax, but it does include all the other kinds of changes. The official release notes have complete explanations and examples. What follows is my quick take on each of the major changes. After that we'll look at new errors and test some of the performance wins.

New Features

Inferred Type Predicates

This was my contribution to TypeScript, and I'm very happy that it's made it into an official release! I've written about it extensively on this blog before, so I won't go into too much more detail here:

Dimitri from Michigan TypeScript even recorded a video of me explaining the story of the feature to him and Josh Goldberg.

TypeScript will infer type predicates for any function where it's appropriate, but I think this will be most useful for arrow functions, the original motivator for this change:

const nums = [1, 2, 3, null];
// ^? const nums: (number | null)[]
const onlyNums = nums.filter(n => n !== null);
// ^? const onlyNums: number[]
// Was (number | null)[] before TS 5.5!

I have two follow-on PRs to expand this feature to functions with multiple return statements and to infer assertion predicates (e.g., (x: string): asserts x is string). I think these would both be nice wins, but it's unclear whether they have a future since the pain points they address are much less common.

Control Flow Narrowing for Constant Indexed Accesses

This is a nice example of improved precision in type checking. Here's the motivating example:

function f1(obj: Record<string, unknown>, key: string) {
if (typeof obj[key] === "string") {
// Now okay, previously was error
obj[key].toUpperCase();
}
}

Previously this would only work for constant property accesses like obj.prop. It's undeniable that this is a win in terms of precision in type checking, but I think I'll keep using the standard workaround: factoring out a variable.

function f1(obj: Record<string, unknown>, key: string) {
const val = obj[key];
if (typeof val === "string") {
val.toUpperCase(); // this has always worked!
}
}

This reduces duplication in the code and avoids a double lookup at runtime. It also gives you an opportunity to give the variable a meaningful name, which will make your code easier to read.

Where I can see myself appreciating this is in single expression arrow functions, where you can't introduce a variable:

keys.map(k => typeof obj[k] === 'string' ? Number(obj[k]) : obj[k])

Regular Expression Syntax Checking

Regular Expressions may be the most common domain-specific language in computing. Previous versions of TypeScript ignored everything inside a /regex/ literal, but now they'll be checked for a few types of errors:

  • syntax errors
  • invalid backreferences to invalid named and numbered captures
  • Using features that aren't available in your target ECMAScript version.

Regexes are notoriously cryptic and hard to debug (this online playground is handy), so anything TypeScript can do to improve the experience of writing them is appreciated.

Since the regular expressions in existing code bases have presumably been tested, you're most likely to run into the third error when you upgrade to TS 5.5. ES2018 added a bunch of new regex features like the /s modifier. If you're using them, but don't have your target set to ES2018 or later, you'll get an error. The fix is most likely to increase your target. The /s (dotAll) flag, in particular, is widely supported in browsers and has been available since Node.js since version 8 (2018). The general rule here is to create an accurate model of your environment, as described in Item 76 of Effective TypeScript.

Regex type checking is a welcome new frontier for TypeScript. I'm intrigued by the possibility that is a small step towards accurately typing the callback form of String.prototype.replace, JavaScript's most notoriously un-type friendly function:

"str".replace(/foo(bar)baz/, (match, capture) => capture);
// ^? (parameter) capture: any

Support for New ECMAScript Set Methods

When you have two sets, it's pretty natural to want to find the intersection, union and difference between them. It's always been surprising to me that JavaScript Sets didn't have this ability built in. Now they do!

While these new methods are stage 4, they haven't been included in any official ECMAScript release yet. (They'll probably be in ES2025.) That means that, to use them in TypeScript, you'll either need to set your target or lib to ESNext. Support for these methods is at around 80% in browsers at the moment, and requires Node.js 22 on the server, so use with caution or include a polyfill.

Isolated Declarations

The isolatedDeclarations setting opens a new front in the should you annotate your return types? debate. The primary motivator here is build speed for very large TypeScript projects. Adopting this feature won't give you a faster build, at least not yet. But it's a foundation for more things to come. If you'd like to understand this feature, I'd highly recommend watching Titian's TS Congress 2023 talk: Faster TypeScript builds with --isolatedDeclarations.

Should you enable this feature? Probably not, at least not yet. An exception might be if you use the explicit-function-return-type rule from typescript-eslint. In that case, switching to isolatedDeclarations will require explicit return type annotations only for your public API, where it's a clearer win.

I expect there will be lots more development around this feature in subsequent versions of TypeScript. I'll also just note that isolatedDeclarations had a funny merge conflict with inferred type predicates. All these new feature are developed independently, which makes it hard to anticipate the ways they'll interact together.

Performance and Size Optimizations

I sometimes like to ask: would you rather have a new feature or a performance win? In this case we get both!

Inferring type predicates does incur a performance hit. In some extreme cases it can be a significant one, but it's typically a 1-2% slowdown. The TypeScript team decided that this was a price they were willing to pay for the feature.

TypeScript 5.5 also includes a whole host of other performance improvements, though, so the net effect is a positive one. You get your feature and your performance, too.

Monomorphization has been an ongoing saga in TypeScript. This is a "death by a thousand cuts" sort of performance problem, which is hard to diagnose because it doesn't show up clearly in profiles. Monomorphization makes all property accesses on objects faster. Because there are so many of these, the net effect can be large.

One of the things we like about objects in JavaScript and TypeScript is that they don't have to fit into neat hierarchies like in Java or C#. But monomorphization is a push towards exactly these sorts of strict hierarchies. It's interesting to see this motivated by performance, rather than design considerations. If anyone tries to translate tsc to the JVM, say, this will help.

I was particularly happy with control flow graph simplifications, since the PR included a screenshot of the TS AST Viewer graph that I built!

These optimizations affect build times, the language service, and TypeScript API consumers. The TS team uses a set of benchmarks based on real projects, including TypeScript itself, to measure performance. I compared TypeScript 5.4 and 5.5 on a few of my own projects in a less scientifically rigorous way:

  • Verifying the 934 code samples in the second edition of Effective TypeScript using literate-ts, which uses the TypeScript API, went from 347→352s. So minimal change, or maybe a slight degradation.
  • Type checking times (tsc --noEmit) were unaffected across all the projects I checked.
  • The time to run webpack on a project that uses ts-loader went from ~43→42s, which is a ~2% speedup.

So no dramatic changes for my projects, but your mileage may vary. If you're seeing big improvements (or regressions), let me know! (If you're seeing regressions, you should probably file a bug against TypeScript.)

Miscellaneous

  • Editor and Watch-Mode Reliability Improvements: These are grungy quality of life improvements, and we should be grateful that the TypeScript team pays attention to them.
  • Easier API Consumption from ECMAScript Modules: I'd always wondered why you couldn't import "typescript" like other modules. Now you can!
  • Simplified Reference Directive Declaration Emit: A weird, dusty corner that no longer exists. Yay!

New Errors

Most of my TypeScript projects had no new errors after I updated to TS 5.5. The only exception was needing to update my target to ES2018 to get the /s regular expression flag, as described above.

Both Bloomberg and Google have posted GitHub issues describing the new errors they ran into while upgrading to TS 5.5. Neither of them ran into major issues.

Conclusions

Every new release of TypeScript is exciting, but the combination of new forms of type inference, isolatedDeclarations, and potential performance wins make this a particularly good one.

It's sometimes said that software dependencies obey a "reverse triangle inequality:" it's easier to go from v1→v2→v3 than it is to go from v1→v3 directly. The idea is that you can fix a smaller set of issues at a time. There's not much reason to hold off on adopting TypeScript 5.5. Doing so now will make upgrading to 5.6 easier in a few months.

Like this post? Consider subscribing to my newsletter, the RSS feed, or following me on Twitter.
Effective TypeScript Book Cover

Effective TypeScript shows you not just how to use TypeScript but how to use it well. Now in its second edition, the book's 83 items help you build mental models of how TypeScript and its ecosystem work, make you aware of pitfalls and traps to avoid, and guide you toward using TypeScript’s many capabilities in the most effective ways possible. Regardless of your level of TypeScript experience, you can learn something from this book.

After reading Effective TypeScript, your relationship with the type system will be the most productive it's ever been! Learn more »