August 12th, 2024

An approach to optimizing TypeScript type checking performance

The article outlines strategies to optimize TypeScript's type checking performance, addressing issues like sluggish IDE responsiveness and compile times, particularly due to performance regressions in TypeScript 5.3.

Read original articleLink Icon
An approach to optimizing TypeScript type checking performance

The article discusses strategies for optimizing TypeScript's type checking performance, particularly in light of performance regressions introduced in TypeScript 5.3. Developers often face challenges with sluggish IDE responsiveness and extended compile times, which can lead to compiler crashes due to memory exhaustion. The article emphasizes the difficulty of troubleshooting type checking issues compared to runtime performance problems, as conventional debugging methods are less applicable. The authors share their experience in measuring and improving type inference performance, particularly in the context of a complex query builder that utilizes numerous overloads for operators. They highlight the importance of effective workflows and tools for performance measurement, noting that the type checker must evaluate all overloads to find matches, which can degrade performance as the number of overloads increases. The article provides insights into their approach, including the use of simplified examples to illustrate the concepts, and acknowledges contributions from colleagues and the TypeScript team for their support in this endeavor.

- TypeScript type checking performance can significantly impact IDE responsiveness and compile times.

- Performance regressions can occur with updates, necessitating effective measurement and optimization strategies.

- Conventional debugging methods are less effective for type checking issues compared to runtime performance problems.

- The number of overloads in TypeScript can affect type checking performance, requiring careful management.

- Collaboration and support from the community can enhance the development of performance optimization strategies.

Link Icon 6 comments
By @scotttrinh - 4 months
Hey, article's author here! Happy to answer any questions, or poke at this general problem with anyone who is interested. Understanding the type checker and its performance is my current personal focus and I find it helpful to bat around ideas with others.
By @tarasglek - 4 months
I really wish to find ts tooling that would show me deltas in ts checker memory usage and tie them to diffs in ci
By @zamadatix - 4 months
Sometimes I wish something akin to Dart (but probably not Dart) had taken off instead of the TypeScript approach. I.e. a JS based language that broke a few things to get types but largely ran on the same VM and could still easily be transpiled in the meantime. Avoid the whole "separate syntax on top of the way the underlying syntax behaves" set of logic.

I suppose WASM enables layered languages like AssemblyScript comes close in many ways but it's also a bit too separated from the primary webpage use case.

By @joseferben - 4 months
excellent article! my approach is to to break down larger bits into smaller monorepo packages with turbo repo where each package builds itself and the task graph is managed by turbo. the drawback is that watching across local packages doesn’t work out of the box.
By @brundolf - 4 months
I've come to believe that sufficiently advanced type inference is indistinguishable from an interpreter

...which has the implication that what TypeScript is actually giving us is a REPL. Our code is increasingly "evaluated" by our IDE, in our hover-overs

I think this is a major reason people like TypeScript so much

By @codeflo - 4 months
These days, TypeScript is effectively nothing more than a high-powered linter. The performance of this linter is so bad that we need to structure our code in a specific way so that we can still afford to run the linter.

Of the performance tips at the end, the interface vs. intersection type one is the suggestion I find the most annoying. That’s because it’s the most common pattern, and using interfaces is conceptually a lot less clean. It’s terrible that a linter effectively forces you into writing worse code.

I really wish the TypeScript team got their act together and fixed the performance of their linter somehow. Finding clever optimizations, porting to Go/Rust, whatever is necessary. (3rd-party reimplementations won’t do: they’ll never catch up with a corporate-funded moving target.)