Skip to content

type checking complexity with multiple template literals in unions #63342

Open

Description

🔎 Search Terms

template literals, performance

🕗 Version & Regression Information

4.1 introduced template literals where tsc fails with RangeError: Map maximum size exceeded.
4.5 is the first version that completes where typechecking time already scales exponentially.

⏯ Playground Link

https://github.com/eps1lon/repro-ts-template-literals-complexity

💻 Code

The example is based on how Next.js typechecks the href in <Link /> components.

// Example type definitions for Next.js routes

type SearchOrHash = `?${string}` | `#${string}`;
type WithProtocol = `${string}:${string}`;

type Suffix = "" | SearchOrHash;

type SafeSlug<S extends string> = S extends `${string}/${string}`
  ? never
  : S extends `${string}${SearchOrHash}`
    ? never
    : S extends ""
      ? never
      : S;

type StaticRoutes =
  | `/next`
  | ...;

// comment out union constituents to see exponential impact of dynamic routes
// 6 routes ->  5  ->  4   -> 3
// tsc on M3:
// 45s      -> 24s -> 12s  -> 6s
// tsgo on M3:
// 9s.      -> 5s  -> 2.5s -> 0.08s
type DynamicRoutes<T extends string = string> =
  | `/${SafeSlug<T>}/${SafeSlug<T>}/${SafeSlug<T>}`
  | `/${SafeSlug<T>}/${SafeSlug<T>}/integrations/${SafeSlug<T>}/${SafeSlug<T>}/resources/${SafeSlug<T>}/${SafeSlug<T>}/billing`
  | ...;

type RouteImpl<T> =
  | StaticRoutes
  | SearchOrHash
  | `${StaticRoutes}${SearchOrHash}`
  | (T extends `${DynamicRoutes<infer _>}${Suffix}` ? T : never);

function Link<RouteType>(href: RouteImpl<RouteType>): void {}

Link("/api/ai-playground/sandbox"); // OK
Link("/new/~/integrations/vercel/front/billing"); // OK

🙁 Actual behavior

Type checking time doubles with every constituent of DynamicRoutes with template literals

🙂 Expected behavior

Type-checking is reasonably fast

Additional information about the issue

No response

Activity

  1. jakebailey commented on Apr 2, 2026

    @jakebailey
    Member

    I'm pretty sure I tried this (no pun intended) in #59759

  2. eps1lon commented on Apr 2, 2026

    @eps1lon
    ContributorAuthor

    What was the conclusion to that? Seems like it fizzled out after memory concerns. Though the benchmarks only showed a noticeable memory increase for "self-build-src - node (v18.15.0, x64)" which I can dig into more if you want.

  3. jakebailey commented on Apr 2, 2026

    @jakebailey
    Member

    The conclusion was "we didn't come to a conclusion before this codebase became closed to new PRs": https://github.com/microsoft/TypeScript#contribute

  4. eps1lon commented on Apr 7, 2026

    @eps1lon
    ContributorAuthor

    There's a Go port: microsoft/typescript-go#3331

  5. added
    Experimentation NeededSomeone needs to try this out to see what happens
    Possible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases
    Domain: check: Big UnionsThe root cause is ultimately that big unions interact poorly with complex structures
    on Apr 7, 2026
  6. eps1lon commented on Aug 20, 2026

    @eps1lon
    ContributorAuthor

    Go port filed against this repo in #63900

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Domain: check: Big UnionsThe root cause is ultimately that big unions interact poorly with complex structuresExperimentation NeededSomeone needs to try this out to see what happensPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions