Wie du die maximale Rekursionstiefe in TypeScript umgehen kannst
Hast du dich schon einmal mit dem „Type instantiation is excessively deep and possibly infinite”-Fehler in TypeScript herumgeärgert? In diesem Blogpost zeige ich dir ein paar originelle Tricks, um das nervige Rekursionslimit ein wenig zu umgehen

Hast du schon einmal diese Fehlermeldung gesehen?
Type instantiation is excessively deep and possibly infinite.
Wenn du schon einmal mit TypeScript 🧙♂️ auseinandergesetzt hast, bist du wahrscheinlich schon über diesen Fehler gestolpert, der sorgt dann meistens für ein paar graue Haare mehr auf dem Kopf. Zum Glück habe ich gleich mehrere Ansätze für dich, wie du den Fehler umgehen kannst bzw. das Limit bis dieser Fehler auftritt anheben kannst. Vorweg sei gesagt, dass der Blog-Beitrag rein informativ von 🧙♂️ zu 🧙♂️ zu verstehen ist und keine Lösung für Produktionscode sein sollte.
Das Problem
TypeScript hat eine fest eingebaute maximale Rekursionstiefe von 1000 (Tail Recursion Depth), die sich nicht ändern lässt. Bei komplexeren Typen, zum Beispiel wenn du versuchst, mit reinen Typen ein Advent-of-Code-Rätsel zu lösen oder wenn du sehr komplexe Typen für eine TypeScript-Library entwickelst, kann diese Grenze relativ schnell erreicht werden.
Schau dir zum Beispiel diesen Replace
-Typ an, der alle Werte in einem Array durch einen übergebenen Wert ersetzt:
type Replace<T extends any[], ReplaceValue> = T extends [any, ...infer Tail]
? [ReplaceValue, ...Replace<Tail, ReplaceValue>]
: [];
Eine reine “head-recursive” Herangehensweise führt zu geschachtelten Tupel- oder Objektstrukturen bei jedem Rekursionsschritt. Denn bei jedem Level wird nicht einfach nur das Problem weitergereicht, sondern gleichzeitig der Ausgabe-Typ aufgebaut, wodurch TypeScript die gesamte Struktur immer weiter expandieren muss.
- Bei jedem Rekursionsschritt wird
[ReplaceValue, ...rest]
hinzugefügt. - Dadurch muss TypeScript jeden Teil des zusammengebauten Typs komplett auflösen und darstellen.
In der Praxis kann es passieren, dass du das Limit schon nach deutlich weniger als 1000 Aufrufen erreichst, weil jede Rekursionsebene sehr tief verschachtelte Typen erzeugt, die wiederum weitere Auswertungen nach sich ziehen.
Der erste 🧙♂️-Trick
Die erste Lösung besteht darin, den Typ “tail-recursive” zu schreiben:
type Replace<T extends any[], ReplaceValue, Agg extends ReplaceValue[] = []> =
T extends [any, ...infer Tail]
? Replace<Tail, ReplaceValue, [...Agg, ReplaceValue]>
: Agg
Bei einer rein “tail-recursive” Version baut jeder Aufruf einen neuen Typ auf, der vom alten abhängt, und die Rekursion summiert sich am Kopf. Indem wir aber einen “Aggregator” Agg
einführen, wechseln wir zu einer Tail Recursion.
Die Idee dahinter ist, dass bei jedem Aufruf das Ergebnis in Agg
gepackt wird, bevor wir sofort zum nächsten Aufruf übergehen. In anderen, echten Compiler-Sprachen ermöglicht das oft eine Optimierung. In TypeScript ist das nicht garantiert, führt aber meist zu einer geringeren Verschachtelungstiefe und erlaubt, etwas größere Arrays zu verarbeiten (trotzdem liegt die effektive Grenze meist noch um 1000 Elemente).
Sollte dein Typ damit noch immer nicht funktionieren, solltest du entweder deinen Ansatz überdenken oder dir ernsthaft Gedanken über deine Lebensentscheidungen machen, die dich an diesen Punkt gebracht haben. Falls du zu dem Entschluss kommst, dass es gar nicht anders geht habe ich hier noch ein bisschen schwarze Magie für dich, die dir weiter helfen sollte.
Der zweite 🧙♂️-Trick
Die erste Abhilfe besteht darin, das “Loop Unrolling” bzw. das Entrollen der Schleife (oder Rekursion) einzusetzen. Ähnlich wie in Sprachen wie C/C++ können wir auch in TypeScript die Rekursion ein Stück weit auseinanderziehen:
type Replace<T extends any[], ReplaceValue, Agg extends any[] = []> =
T extends [any, any, ...infer Tail]
? Replace<Tail, ReplaceValue, [...Agg, ReplaceValue, ReplaceValue]>
: T["length"] extends 1 ? [...Agg, ReplaceValue] : Agg
Indem wir hier die Rekursion um jeweils zwei Schritte entrollen, erhöhen wir das effektive Rekursionslimit um den Faktor 2. So können jetzt etwa ~2000 Elemente verarbeitet werden. Du könntest auch 11 Schritte auf einmal entrollen, doch dann stößt du aber auf eine neue Fehlermeldung:
Type produces a tuple type that is too large to represent.(2799)
Dieser Fehler kann auch umgangen werden, ist aber nicht Gegenstand dieses Blog-Beitrags.
Das geht übrigens auch mit Strings
type Replace<T extends string, ReplaceValue, Agg extends string = ""> =
T extends `${infer _}${infer _}${infer Tail}`
? Replace<Tail, ReplaceValue, `${Agg}${ReplaceValue}${ReplaceValue}`>
: T["length"] extends 1 ? `${Agg}${ReplaceValue}` : Agg;
Der dritte 🧙♂️-Trick
Intern verwendet TypeScript einen Zähler, um die Rekursionstiefe mitzuzählen. Durch ein Intersection- Type kann dieser Zähler auf null zurückgesetzt werden. Diese Technik lässt sich mehrmals anwenden, verlangsamt aber auch den Compiler:
type Replace<T extends any[], ReplaceValue, Agg extends any[] = [], RecursionCount extends any[] = []> = RecursionCount["length"] extends 500
? Replace<T, ReplaceValue, Agg, []> & {} // Zähler zurücksetzen
: T extends [any, ...infer Tail]
? Replace<Tail, ReplaceValue, [...Agg, ReplaceValue], [...RecursionCount, unknown]>
: Agg;
Das ist zwar nicht so performant wie die erste Methode, bringt dich aber auf ungefähr >2000 Elemente, bevor du auf den Fehler “Type Instantiation Depth (TS2589)” stößt. Mehr Details dazu findest du hier.
Der vierte 🧙♂️-Trick
Lass es einfach bleiben. Denk daran: TypeScript wird von Entwickler für Entwickler gemacht. Es kann zwar die DX verbessern, aber wenn du tsc
zu sehr ausbremst, werden alle in deinem Projekt dich hassen — und wenn du das Ganze dann noch auf npm veröffentlichst, werden die Leute außerhalb deines Betriebs dich dafür verfluchen. Die Entwickler, die in besonders großen Codebasen Tools wie tRPC oder Zod verwenden müssen, wissen (leider) was ich meine. Meistens brauchst du solche Spielereien gar nicht. Halte es einfach. Wenn du es nur zum Spaß machst, um Nerds wie mich zu beeindrucken, dann tob dich aus, schreibe Typen bei denen jeder andere den Kopf schüttelt :).
Fazit
Diese Workarounds helfen dir dabei, das Maximum-Recursion-Depth-Problem in TypeScript zu umgehen. Ob du nun eine Tailrecursion, die Schleife abrollst oder den Zähler zurücksetzt – alle Methoden können dir einen tieferen Einblick in die Mechanik von Typescript geben. Wenn du noch andere Ideen hast, wie man diese Beschränkung aushebeln kann, lass es mich gerne wissen. Schreib mir einfach auf X.











