Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improve Typescript speed when a type is any member of a map?

E.g., I have these types:

class User extends Entity {}

class Post extends Entity {}

type Entities = {
  user: User,
  post: Post,
  // potentially hundreds more
};

type EntityType = 'user' | 'post' | ...;

When I use Entities and EntityType in generics, it noticeably slows down Typescript. It takes several minutes just to display type information in VS Code. I have generic functions like:

function getEntityFromCache<T extends EntityType>(type: T, id: number): Entities[T] | null | undefined;

function getEntity<T extends EntityType>(type: T, id: number): Entities[T] | null {
  const cachedEntity = getEntityFromCache(type, id);
  if (cachedEntity !== undefined) {
    return cachedEntity;
  }
  ...
}

My understanding of why this is slow is because Entities[T] is a union of all the values of Entities, i.e. User | Post | .... TS doesn't handle unions well. To check that the return type of cached is correct, TS has to iterate through every possible value of T. I.e. if T = 'user', verify that cachedEntity is Entities['user']; if T = 'post', verify that cachedEntity is Entities['post']; and so on. It's slow because TS has to check every value of the union every time I have a generic.

In addition, rather than just comparing generics values ('user' === 'user', 'post' === 'post'), TS compares the entire entity (User === User, Post === Post). This makes it worse because the entities are complicated.

What are some ways to speed this up? Here are some ideas I have:

  1. Remove generics from the intermediate functions and make them return the base entity type, then type cast it. E.g. function getEntityFromCache(type: T, id: number): Entity | null | undefined;

  2. Somehow make TS compare the entity type string instead of comparing the entire entities.

like image 491
Leo Jiang Avatar asked Oct 16 '25 03:10

Leo Jiang


1 Answers

According to TypeScript wiki page Preferring Base Types Over Unions It is worth using subtypes, rather than unions.

However, they also come with a cost. Every time an argument is passed to printSchedule, it has to be compared to each element of the union. For a two-element union, this is trivial and inexpensive. However, if your union has more than a dozen elements, it can cause real problems in compilation speed. For instance, to eliminate redundant members from a union, the elements have to be compared pairwise, which is quadratic. This sort of check might occur when intersecting large unions, where intersecting over each union member can result in enormous types that then need to be reduced. One way to avoid this is to use subtypes, rather than unions.

Reduced example from the docs:


interface A {
  char: 'a' | 'b' | 'c' | 'd' | 'e';
}

interface B extends A {
  char: 'a' | 'b';
}

interface C extends A {
  char: 'd' | 'e';
}


declare function char(schedule: A): void;
like image 170
captain-yossarian Avatar answered Oct 17 '25 19:10

captain-yossarian