Das infer-Schlüsselwort: Typen aus anderen Typen extrahieren
<T>
type
TypeScript · Type Inference · Conditional Types · Utility Types
Das infer-Schlüsselwort
Typen aus anderen Typen extrahieren

Das infer Schlüsselwort ist der Schlüssel zu vielen TypeScript Utility Types und erlaubt es, Teiltypen wie das Resultat eines Promise oder die Parameter einer Funktion direkt aus einem bestehenden Typ zu extrahieren, ohne sie manuell zu duplizieren. Wer versteht, wie infer innerhalb conditional types arbeitet, kann eigene Utility Types bauen, Duplikate vermeiden und Typen automatisch synchron zu ihrer Quelle halten.

12 Min. Lesezeit infer · Conditional Types · Utility Types TypeScript 5.x · ReturnType · Awaited

1. Warum infer für TypeScript Entwickler unverzichtbar ist

Wer aus der Magento- und PHP-Welt kommt, kennt das Problem: Ein Rückgabetyp ändert sich in einer Methode, und an zehn anderen Stellen im Code muss der Aufrufer manuell nachziehen. TypeScript löst dieses Problem in der Theorie durch statische Typisierung, aber ohne infer tappt man in dieselbe Falle: Man dupliziert Typdefinitionen von Hand, weil es keinen Weg gibt, einen Teiltyp aus einem bestehenden Typ herauszuziehen. Das infer Schlüsselwort schließt genau diese Lücke.

Praktisch bedeutet das: Statt den Rückgabetyp einer Funktion, den aufgelösten Wert eines Promise oder die Parameter einer Callback-Signatur erneut zu tippen, lässt man TypeScript den Typ selbst ableiten. Das Ergebnis bleibt automatisch synchron mit der Quelle, der Compiler schlägt bei jeder inkompatiblen Änderung sofort Alarm, und Utility Types wie ReturnType, Parameters oder Awaited aus der TypeScript-Standardbibliothek basieren intern exakt auf diesem Mechanismus.

2. Die Mechanik: Wie infer innerhalb conditional types funktioniert

infer existiert ausschließlich innerhalb der extends-Klausel eines conditional type. Die allgemeine Form T extends U ? X : Y prüft, ob T strukturell zu U passt. Ersetzt man einen Teil von U durch infer Name, weist man TypeScript an, an genau dieser Position eine neue Typvariable zu deklarieren und sie mit dem Teil von T zu füllen, der dort strukturell passt. Diese Variable steht danach im true-Zweig (X) zur Verfügung, so wie eine Capture Group in einem regulären Ausdruck den passenden Teilstring merkt.

Wichtig ist die Reihenfolge: Der Compiler versucht zuerst, T gegen die Struktur mit dem infer-Platzhalter zu matchen. Schlägt das Pattern Matching fehl, greift der false-Zweig, und die inferierte Variable existiert dort nicht. Genau das macht infer zu einem strukturellen Werkzeug: Es beschreibt eine Form, keine konkrete Bedingung, und TypeScript löst die Unbekannte durch Unifikation gegen den tatsächlichen Typ auf.


// Basic conditional type using infer to capture a sub-type
type ElementType<T> = T extends (infer U)[] ? U : never;

type A = ElementType<string[]>;   // string
type B = ElementType<number[]>;   // number
type C = ElementType<boolean>;    // never, boolean is not an array

// infer only works inside the "extends" clause of a conditional type
// T extends (infer U)[] ? U : never
//              ^^^^^^^ captures the array element type as U

3. Promise-Typen extrahieren: Das Resultat eines Promise erfassen

Der klassische Einstiegsfall für infer ist das Extrahieren des aufgelösten Werts eines Promise. Ein async Client liefert oft Promise<SomeResponse> zurück, aber an vielen Stellen im Code braucht man nur SomeResponse, etwa um einen Parameter oder ein Feld in einem Formular zu typisieren. Mit T extends Promise<infer U> ? U : T lässt sich dieser innere Typ ohne manuelle Zusatzarbeit herausziehen, und die Definition bleibt korrekt, selbst wenn sich der Rückgabetyp der ursprünglichen Funktion später ändert.

Die naive Variante hat jedoch eine Schwäche: Verschachtelte Promises wie Promise<Promise<string>> werden nur einmal aufgelöst. Genau dieses Problem löst das eingebaute Awaited<T> Utility Type durch Rekursion, indem es sich innerhalb des true-Zweigs erneut selbst aufruft, bis kein Promise mehr übrig bleibt. Diese rekursive Anwendung von infer ist ein Muster, das in vielen fortgeschrittenen Utility Types wiederkehrt.


// Extract the resolved type of a Promise
type Unwrap<T> = T extends Promise<infer U> ? U : T;

type A = Unwrap<Promise<string>>;         // string
type B = Unwrap<Promise<{ id: number }>>; // { id: number }
type C = Unwrap<number>;                  // number, not a promise, returned as-is

// Naive Unwrap does not resolve nested promises
type D = Unwrap<Promise<Promise<string>>>; // Promise<string>, still wrapped once

// Recursive version resolves nested promises fully, like the built-in Awaited<T>
type DeepUnwrap<T> = T extends Promise<infer U> ? DeepUnwrap<U> : T;
type E = DeepUnwrap<Promise<Promise<string>>>; // string

4. Funktionstypen extrahieren: Parameter und Rückgabewerte

Funktionssignaturen lassen sich mit infer ebenso gezielt zerlegen wie Promises. Die Struktur T extends (...args: infer P) => unknown ? P : never erfasst die komplette Parameterliste als Tupel, inklusive Namen und optionaler Parameter, weil TypeScript die Rest-Parameter-Syntax auf Typebene genauso versteht wie zur Laufzeit. Das ist besonders nützlich, wenn man einen Wrapper um eine bestehende Funktion baut und sicherstellen will, dass sich die Signatur des Wrappers automatisch anpasst, sobald sich die Originalfunktion ändert.

Man kann die Position des infer-Platzhalters gezielt verschieben, um nur einen Teil der Parameter zu erfassen, etwa nur den ersten Parameter über (first: infer F, ...rest: unknown[]) => unknown. Diese Präzision unterscheidet infer von einer reinen Kopie der Signatur: Man beschreibt exakt, welcher Ausschnitt der Struktur interessant ist, und überlässt den Rest der Struktur bewusst dem Platzhalter unknown.


// Extract the parameter tuple of a function type
type Params<T> = T extends (...args: infer P) => unknown ? P : never;

function createOrder(customerId: string, items: string[], priority: boolean) {
  return { customerId, items, priority };
}

type CreateOrderParams = Params<typeof createOrder>;
// [customerId: string, items: string[], priority: boolean]

// Extract just the first parameter
type FirstParam<T> = T extends (first: infer F, ...rest: unknown[]) => unknown
  ? F
  : never;

type FirstArg = FirstParam<typeof createOrder>; // string

5. Eigene ReturnType- und Awaited-Utilities selbst bauen

Am besten versteht man infer, indem man die eingebauten Utility Types einmal selbst nachbaut. Eine vereinfachte Version von ReturnType<T> braucht nur eine Constraint, die sicherstellt, dass T überhaupt eine Funktion ist, und einen conditional type, der den Rückgabetyp über infer R an der Position nach dem Pfeil erfasst. Der eingebaute Typ in lib.es5.d.ts unterscheidet sich kaum von dieser minimalen Implementierung, er ist im Kern genau dieses eine Pattern.

Für Awaited<T> kombiniert man infer mit Rekursion: Der conditional type prüft, ob der inferierte Typ U selbst wieder ein Promise ist, und ruft sich in diesem Fall erneut mit U auf, statt sofort zurückzugeben. Diese Selbstbezüglichkeit ist in TypeScript seit einigen Versionen explizit erlaubt und wird vom Compiler mit einer Rekursionstiefe begrenzt, um Endlosschleifen im Typsystem zu verhindern.


// A simplified version of the built-in ReturnType<T> utility
type MyReturnType<T extends (...args: never[]) => unknown> =
  T extends (...args: never[]) => infer R ? R : never;

function fetchOrder(id: string) {
  return { id, total: 42, currency: "EUR" };
}

type Order = MyReturnType<typeof fetchOrder>;
// { id: string; total: number; currency: string }

// A simplified version of the built-in Awaited<T> utility
type MyAwaited<T> = T extends Promise<infer U>
  ? U extends Promise<unknown>
    ? MyAwaited<U>
    : U
  : T;

async function fetchOrderAsync(id: string) {
  return fetchOrder(id);
}

type AsyncOrder = MyAwaited<ReturnType<typeof fetchOrderAsync>>;
// { id: string; total: number; currency: string }

6. Mehrfaches infer und Union Distribution

Ein conditional type ist nicht auf ein einziges infer beschränkt. T extends (...args: infer P) => infer R ? [P, R] : never erfasst Parameter und Rückgabetyp in einem einzigen Durchlauf und liefert beide als Tupel zurück. Jede infer-Position wird unabhängig gegen die passende Stelle der Struktur von T aufgelöst, solange das gesamte Muster strukturell zu T passt. Das erlaubt kompakte Utility Types, die mehrere Informationen gleichzeitig aus einer einzigen Signatur ziehen, ohne den Typ zweimal zu durchlaufen.

Eine Besonderheit betrifft distributive conditional types: Steht ein nackter Typparameter direkt vor extends, verteilt TypeScript den conditional type automatisch über jedes Mitglied einer Union. Bei type Wrapped<T> = T extends Promise<infer U> ? U : T, angewendet auf Promise<string> | Promise<number>, wird infer also für jedes Union-Mitglied einzeln ausgewertet, und das Ergebnis ist die Union string | number, nicht ein einzelner unaufgelöster Typ.

7. Grenzen und Fallstricke von infer

infer ist strikt an den extends-Zweig eines conditional type gebunden und kann nirgendwo sonst stehen, weder in einer eigenständigen Typdeklaration noch in einem Interface. Der häufigste Anfängerfehler ist der Versuch, infer außerhalb dieses Kontexts zu verwenden, was TypeScript mit einem klaren Compilerfehler quittiert. Ein zweiter Stolperstein: Taucht dieselbe infer-Variable mehrfach in kontravarianter Position auf, etwa in mehreren Funktionsparametern, bildet der Compiler statt einer Union eine Intersection, was zu überraschend engen Typen führen kann.

Auch die Fehlerdiagnose ist anspruchsvoller als bei normalen Typfehlern: Passt die Struktur nicht, landet man häufig im false-Zweig und damit bei never, ohne eine aussagekräftige Fehlermeldung an der eigentlichen Ursache. Tief verschachtelte, rekursive infer-Konstruktionen können außerdem die Typprüfung spürbar verlangsamen und in Extremfällen die von TypeScript intern begrenzte Rekursionstiefe überschreiten. Ein guter Kompromiss ist, komplexe infer-Ketten in benannte Zwischenschritte aufzuteilen, statt alles in einem einzigen, kaum lesbaren Typausdruck unterzubringen.

8. Praxis: Formulare, API Clients und Event Systeme

In der Praxis taucht infer selten isoliert auf, sondern eingebettet in größere Utility Types produktiver Bibliotheken. Formular-Bibliotheken leiten den Typ eines Feldwerts aus einem Validierungsschema ab, typsichere API Clients extrahieren den Response-Typ direkt aus der Definition eines Endpunkts, und Event-Systeme erfassen den Payload-Typ eines Listeners, ohne ihn an der Aufrufstelle erneut aufzuschreiben. In allen drei Fällen verhindert infer, dass Laufzeitverhalten und Typdefinition auseinanderlaufen.

Ein typisches Beispiel ist ein typisierter Event Bus, bei dem der Payload-Typ eines Listeners automatisch aus dessen Funktionssignatur abgeleitet wird, statt ihn separat zu deklarieren. Registriert man einen Listener für ein bestimmtes Event, kennt TypeScript dank infer und Mapped Types bereits die exakte Form des Payloads, inklusive Autovervollständigung im Editor. Das reduziert nicht nur Tipparbeit, sondern verhindert eine ganze Klasse von Fehlern, bei denen Payload und Listener-Signatur stillschweigend auseinanderdriften.


// Practical example: a typed event bus derives payload types via infer
type EventMap = {
  "order:created": { orderId: string; total: number };
  "order:cancelled": { orderId: string; reason: string };
};

type Listener<T> = (payload: T) => void;

// Extract the payload type straight out of the listener signature
type PayloadOf<L> = L extends Listener<infer P> ? P : never;

class TypedEventBus<Events extends Record<string, unknown>> {
  private listeners: { [K in keyof Events]?: Listener<Events[K]>[] } = {};

  on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void {
    (this.listeners[event] ??= []).push(listener);
  }

  emit<K extends keyof Events>(event: K, payload: Events[K]): void {
    this.listeners[event]?.forEach((listener) => listener(payload));
  }
}

const bus = new TypedEventBus<EventMap>();
bus.on("order:created", (payload) => {
  // payload is inferred as { orderId: string; total: number }
  console.log(payload.orderId);
});

9. infer im Vergleich: manuell vs. automatisch abgeleitet

Die folgende Übersicht stellt den manuellen Ansatz, bei dem Typen von Hand dupliziert werden, dem infer-basierten Ansatz gegenüber, bei dem TypeScript den Typ automatisch aus der Quelle ableitet. Der Unterschied wirkt auf den ersten Blick klein, hat aber direkten Einfluss auf die Wartbarkeit größerer Codebasen.

Anwendungsfall Manuell dupliziert Mit infer abgeleitet Vorteil
Promise-Resultat type Result = User; Awaited<ReturnType<typeof fn>> Bleibt bei Signaturänderung korrekt
Rückgabetyp einer Funktion type Return = Order; ReturnType<typeof fn> Kein Duplikat, immer aktuell
Funktionsparameter type Args = [string, number]; Parameters<typeof fn> Erfasst auch optionale Parameter
Array-Elementtyp type Item = Product; T extends (infer U)[] ? U : never Funktioniert generisch für jedes Array
Objekt-Wertetyp type Value = string; T extends Record<string, infer V> ? V : never Passt sich bei neuen Feldern automatisch an

In jeder Zeile der Tabelle gilt dasselbe Prinzip: Der manuelle Ansatz funktioniert nur so lange, bis sich die Quelle ändert und niemand daran denkt, die Kopie nachzuziehen. Der infer-basierte Ansatz bleibt dagegen automatisch korrekt, weil der abgeleitete Typ strukturell an seine Quelle gebunden ist und sich bei jeder Änderung von selbst aktualisiert.

Mironsoft

TypeScript Code-Review, Utility-Type-Refactoring und typsichere API-Clients für Magento- und Hyvä-Projekte

Bereit für typsicheren TypeScript-Code?

Wir reviewen euren TypeScript-Code, bauen wartbare Utility Types mit infer und conditional types, und sorgen dafür, dass API Clients und Formulare durchgängig typsicher bleiben, von der Magento-Integration bis zum Frontend-Build.

TypeScript Code-Review

Konsistente, wartbare Typdefinitionen statt Duplikaten und any

Utility-Type-Refactoring

Eigene infer-basierte Utility Types statt copy-paste Typen

Typsichere API-Clients

Response- und Request-Typen automatisch aus Schemas abgeleitet

10. Zusammenfassung

Das infer Schlüsselwort löst ein sehr konkretes Problem: Typen aus bestehenden Typen ableiten, statt sie manuell zu duplizieren. Es funktioniert ausschließlich innerhalb der extends-Klausel eines conditional type, wo es wie eine Capture Group eine Typvariable an einer bestimmten strukturellen Position deklariert und mit dem passenden Teil des untersuchten Typs füllt. Die eingebauten Utility Types ReturnType, Parameters und Awaited sind im Kern nichts anderes als kompakte, gut getestete Anwendungen genau dieses Mechanismus.

Wer eigene Utility Types mit infer baut, sollte auf drei Dinge achten: die Grenzen des Mechanismus kennen, also kein infer außerhalb eines conditional type verwenden, die Auswirkungen der distributiven Auswertung bei Union-Typen einplanen, und komplexe Konstruktionen in benannte, lesbare Zwischenschritte aufteilen. Richtig eingesetzt sorgt infer dafür, dass abgeleitete Typen automatisch mit ihrer Quelle synchron bleiben, ganz ohne zusätzlichen Wartungsaufwand bei jeder Änderung.

Das infer-Schlüsselwort - Das Wichtigste auf einen Blick

Mechanik

infer deklariert eine Typvariable ausschließlich innerhalb der extends-Klausel eines conditional type und erfasst die passende Teilstruktur.

Promise & Awaited

Der aufgelöste Wert eines Promise lässt sich mit infer extrahieren, verschachtelte Promises benötigen eine rekursive Variante wie Awaited.

Funktionen

Parameter und Rückgabetyp einer Funktion lassen sich einzeln oder gemeinsam über mehrere infer-Positionen erfassen.

Grenzen

infer funktioniert nicht standalone, verteilt sich bei Unions automatisch und sollte in komplexen Ketten in Zwischenschritte aufgeteilt werden.

11. FAQ: Das infer-Schlüsselwort in TypeScript

1Was macht das infer-Schlüsselwort in TypeScript?
infer deklariert innerhalb der extends-Klausel eines conditional type eine neue Typvariable, die mit dem passenden Teil des untersuchten Typs gefüllt wird, ähnlich einer Capture Group in einem regulären Ausdruck.
2Warum lässt sich infer nicht außerhalb eines conditional type verwenden?
infer ist syntaktisch an den extends-Zweig gebunden, weil TypeScript die Typvariable nur während des Pattern Matchings gegen T auflösen kann. Ohne diesen Kontext fehlt die Referenzstruktur.
3Wie extrahiert man den aufgelösten Typ eines Promise mit infer?
Mit T extends Promise<infer U> ? U : T. Für verschachtelte Promises braucht man eine rekursive Variante, die sich selbst erneut mit U aufruft, genau wie Awaited<T>.
4Was ist der Unterschied zwischen Parameters<T> und ReturnType<T>?
Parameters erfasst über infer die Parameterliste als Tupel, ReturnType erfasst über infer den Rückgabetyp nach dem Pfeil. Beide basieren auf demselben Mechanismus, nur an unterschiedlichen Positionen.
5Was bedeutet Union Distribution bei infer?
Steht ein nackter Typparameter direkt vor extends, wertet TypeScript den conditional type für jedes Mitglied einer Union separat aus, infer wird pro Mitglied einzeln aufgelöst.
6Kann ein conditional type mehrere infer-Positionen gleichzeitig enthalten?
Ja, etwa T extends (...args: infer P) => infer R ? [P, R] : never. Jede infer-Position wird unabhängig gegen die passende Stelle der Struktur aufgelöst.
7Was passiert, wenn dieselbe infer-Variable mehrfach in kontravarianter Position auftaucht?
Der Compiler bildet dann eine Intersection statt einer Union der möglichen Typen, was zu überraschend engen, kaum nutzbaren Typen führen kann.
8Warum landet ein Typ manchmal unerwartet bei never?
Passt die Struktur nicht auf das infer-Muster, greift der false-Zweig, der häufig never zurückgibt, meist ein Zeichen für eine nicht erwartete Eingabeform.
9Wie baut man eine eigene, vereinfachte Version von ReturnType?
Mit einer Constraint für Funktionen und einem conditional type wie T extends (...args: never[]) => infer R ? R : never, der den Rückgabetyp nach dem Pfeil erfasst.
10In welchen Praxisfällen lohnt sich infer besonders?
Typsichere API Clients, Formular-Bibliotheken mit Schema-Validierung und Event-Systeme, deren Payload-Typ direkt aus der Listener-Signatur abgeleitet wird.