A couple of years ago, Sentry were having big problems with their React app. They'd pretty recently migrated it to TypeScript from JavaScript. And the app was part of a large monorepo.

But the IDE performance was slow. You'd often need to wait a couple of seconds after making a change for the TypeScript language server to update. And running tsc would take a long time.

Now, this isn't unusual for a large TypeScript codebase. But the Sentry team had a hunch that something was wrong. The problem felt out of proportion to the size of the codebase.

It turned out that the issue, outlined by Jonas, was down to a single pattern.

In tons of places in Sentry's codebase, they were extending HTML types in React. For instance, defining ButtonProps would look like this:

import React from "react";

type ButtonProps =
  React.HTMLAttributes<HTMLButtonElement> & {
    extraProp: string;
  };

const Button = ({ extraProp, ...props }: ButtonProps) => {
  console.log(extraProp);
  return <button {...props} />;
};

This means that you could pass in all the props that a <button> element could take, plus an extraProp:

<Button
  extraProp="whatever"
  onClick={(e) => {

(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>

}} />;

But it turns out that this pattern is devilishly slow. So Jonas, following the advice of the TypeScript Performance Wiki, changed each of these to use an interface instead:

import React from "react";

interface ButtonProps
  extends React.HTMLAttributes<HTMLButtonElement> {
  extraProp: string;
}

Suddenly, things got a lot snappier. The TypeScript language server was faster, and tsc ran quicker. Just from a little syntax change. Why?

You may have heard that interface is slightly faster than type. This is not quite true. In fact, interface extends is slightly faster than &.

In an earlier version of this article, I'd posted an explanation based on some fuzzy thinking which, thanks to my old colleague Mateusz Burzyński, I now understand was wrong.

The problem is more complex than I realised - check out this thread for his critiques and our investigations.

Hopefully, I can update this article again with a definitive description of why this happens - but nothing is simple when it comes to TypeScript performance.

Suffice to say - interface extends is generally faster than &, and so it proved in this case too.