Den never Type in TypeScript praktisch nutzen
AI generated
<T>
type
TypeScript · Typsystem · Fehlerbehandlung
Den never Type praktisch nutzen
mehr als eine theoretische Randnotiz im Typsystem

Viele TypeScript Entwickler kennen den never Type nur als abstrakte Kuriositaet aus der Dokumentation, obwohl er eines der wirkungsvollsten Werkzeuge fuer Exhaustiveness Checking und typsichere Fehlerbehandlung ist. Wer never gezielt einsetzt, laesst den Compiler unmoegliche Zustaende erkennen, statt sie erst zur Laufzeit zu entdecken.

16 Min. Lesezeit never · Exhaustiveness · Conditional Types TypeScript 5.x

1. Was der never Type wirklich bedeutet

Der never Type ist der sogenannte Bottom Type des TypeScript Typsystems: er repraesentiert eine Menge, die keinen einzigen Wert enthaelt. Waehrend unknown jeden moeglichen Wert zulaesst und damit der Top Type ist, beschreibt never genau das Gegenteil, einen Typ, dem theoretisch niemals ein Wert zugewiesen werden kann. Diese Definition klingt zunaechst abstrakt, hat aber sehr konkrete praktische Anwendungen, sobald man versteht, wo never im Typsystem natuerlich entsteht.

never taucht an zwei grundsaetzlich unterschiedlichen Stellen auf: als expliziter Rueckgabetyp fuer Funktionen, die niemals normal zurueckkehren, etwa weil sie immer werfen oder eine Endlosschleife ausfuehren, und als impliziter Typ, der entsteht, wenn TypeScript alle moeglichen Faelle einer Union bereits ausgeschlossen hat. Der zweite Fall ist der praktisch wertvollste: er macht Exhaustiveness Checking ueberhaupt erst moeglich, weil ein verbleibender never Typ signalisiert, dass wirklich keine Variante mehr uebrig ist.

Ein haeufiges Missverstaendnis ist, never mit void zu verwechseln. void bedeutet, dass eine Funktion zwar zurueckkehrt, aber keinen sinnvollen Wert liefert, etwa eine Funktion, die nur eine Nebenwirkung ausfuehrt und implizit undefined zurueckgibt. never hingegen bedeutet, dass die Funktion ueberhaupt nicht bis zu einem Rueckgabepunkt gelangt. Diese Unterscheidung ist mehr als Begriffshaarspalterei, sie hat direkte Konsequenzen fuer die Kontrollfluss Analyse des Compilers.

2. never bei Funktionen, die niemals zurueckkehren

Der klassische Anwendungsfall fuer einen expliziten never Rueckgabetyp ist eine Funktion, die garantiert immer wirft, etwa eine zentrale Fehlerbehandlungsfunktion, die eine Exception erzeugt und wirft, aber niemals normal zurueckkehrt. Wird eine solche Funktion mit dem Rueckgabetyp never annotiert, kann TypeScript diese Information in der Kontrollfluss Analyse an der Aufrufstelle nutzen: Code nach dem Aufruf dieser Funktion wird als unerreichbar erkannt, was sich direkt auf nachfolgende Typverfeinerungen auswirkt.

Ein zweiter, selten diskutierter Anwendungsfall ist eine Funktion mit einer garantierten Endlosschleife, etwa der Haupt Event Loop eines Servers oder eine while (true) Schleife, die niemals durch break oder return verlassen wird. Auch diese Funktion kann korrekt mit never annotiert werden, weil sie tatsaechlich niemals einen Rueckgabewert produziert. TypeScript leitet diesen Rueckgabetyp in vielen Faellen automatisch ab, eine explizite Annotation dokumentiert die Absicht aber zusaetzlich fuer menschliche Leser.

Praktisch wertvoll wird dieses Muster vor allem in Kombination mit Guard Funktionen, die eine Vorbedingung pruefen und im Fehlerfall werfen. Eine Funktion assertIsDefined(value: T | undefined): asserts value is T kombiniert diesen Effekt mit Type Assertions: Nach ihrem Aufruf weiss TypeScript, dass der Wert definiert ist, weil die Funktion im gegenteiligen Fall geworfen und damit den Kontrollfluss beendet haette.


// A function annotated with never never returns normally
function raise(message: string): never {
  throw new Error(message);
}

function getConfigValue(config: Record<string, string>, key: string): string {
  const value = config[key];
  if (value === undefined) {
    raise(`Missing required config key: ${key}`);
  }
  // TypeScript knows control flow cannot reach here without a defined value
  return value;
}

// A never-returning infinite loop, useful for a worker's main loop
function runForever(tick: () => void): never {
  while (true) {
    tick();
  }
}

// Assertion function combining never with type narrowing
function assertIsDefined<T>(value: T | undefined, message: string): asserts value is T {
  if (value === undefined) {
    throw new Error(message);
  }
}

3. Exhaustiveness Checking mit never in switch Statements

Der praktisch wichtigste Anwendungsfall des never Type ist Exhaustiveness Checking bei der Verarbeitung von Discriminated Unions. Das Muster ist immer gleich: eine kleine Hilfsfunktion assertUnreachable(value: never): never wird im default Zweig eines switch aufgerufen. Solange alle Varianten der Union in vorherigen case Zweigen behandelt wurden, bleibt an dieser Stelle tatsaechlich nichts mehr uebrig, der verbleibende Typ ist never, und der Aufruf kompiliert anstandslos.

Der eigentliche Nutzen zeigt sich, sobald die Union spaeter um eine neue Variante erweitert wird. TypeScript berechnet an der default Stelle den verbleibenden Typ neu und stellt fest, dass die neue, noch unbehandelte Variante uebrig bleibt. Da diese Variante kein never mehr ist, schlaegt der Aufruf von assertUnreachable mit einem klaren Typfehler fehl, lange bevor der Code ueberhaupt ausgefuehrt wird. Dieser Mechanismus verwandelt eine potenzielle Laufzeit Ueberraschung in einen Compile Zeit Fehler.

Ein Detail, das die Wirksamkeit dieses Musters ausmacht: der Parametertyp von assertUnreachable muss exakt never sein, nicht unknown oder any. Nur never erzwingt, dass an dieser Stelle tatsaechlich kein verbleibender Typ mehr existieren darf, waehrend unknown jeden beliebigen Restwert klaglos akzeptieren wuerde und die gesamte Absicherung wirkungslos machen wuerde.


function assertUnreachable(value: never): never {
  throw new Error(`Unhandled case: ${JSON.stringify(value)}`);
}

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "rectangle"; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
    case "rectangle":
      return shape.width * shape.height;
    default:
      // If a new Shape variant is added without a matching case above,
      // "shape" is no longer never here, and this line fails to compile
      return assertUnreachable(shape);
  }
}

4. never in Conditional Types und Type Filtering

Ueber den Kontext von Werten hinaus spielt der never Type eine zentrale Rolle in Conditional Types, weil er als das Ergebnis fungiert, das effektiv "diesen Zweig herausfiltern" bedeutet. Distributive Conditional Types, also Conditional Types, die ueber eine Union von Typen verteilt ausgewertet werden, nutzen never haeufig, um bestimmte Varianten aus einer Union komplett zu entfernen, weil eine Union mit never als Mitglied automatisch dieses Mitglied verwirft.

Dieses Verhalten ist die Grundlage vieler eingebauter Utility Types. Exclude<T, U> ist im Kern definiert als T extends U ? never : T, verteilt ueber jedes Mitglied der Union T. Fuer jedes Mitglied, das der Bedingung extends U entspricht, wird never zurueckgegeben, und da eine Union automatisch never Mitglieder entfernt, verschwinden genau diese Mitglieder aus dem Ergebnis. NonNullable<T> funktioniert nach demselben Prinzip, indem null und undefined gezielt auf never abgebildet werden.

Ein praktischer Anwendungsfall jenseits der eingebauten Utility Types ist das Filtern von Objektschluesseln anhand ihres Werttyps. Ein Mapped Type kann jeden Schluessel, dessen Werttyp nicht zu einer Bedingung passt, auf never abbilden und anschliessend mit keyof in Kombination mit [keyof T] nur die tatsaechlich passenden Schluesselnamen extrahieren. Dieses Muster wird haeufig genutzt, um etwa nur die Methodennamen oder nur die string Felder eines Interfaces als eigenen Typ zu isolieren.


// never filters out union members inside conditional types
type Exclude2<T, U> = T extends U ? never : T;

type Status = "idle" | "loading" | "success" | "error";
type NonErrorStatus = Exclude2<Status, "error">; // "idle" | "loading" | "success"

// Extracting only the keys whose value type matches a condition
type KeysOfType<T, ValueType> = {
  [K in keyof T]: T[K] extends ValueType ? K : never;
}[keyof T];

interface Product {
  id: number;
  name: string;
  price: number;
  isActive: boolean;
  description: string;
}

type StringKeys = KeysOfType<Product, string>; // "name" | "description"
type NumberKeys = KeysOfType<Product, number>; // "id" | "price"

5. never als Sentinel fuer unmoegliche Zustaende

Ueber Exhaustiveness Checking hinaus laesst sich der never Type gezielt einsetzen, um im Typsystem festzulegen, dass ein bestimmter Zustand strukturell unmoeglich sein soll. Ein Beispiel ist ein generischer Cache Typ, der entweder einen Wert oder einen Fehler enthaelt, niemals beides gleichzeitig. Statt zwei optionale Felder zu verwenden, kann ein generischer Typ mit zwei Varianten modelliert werden, bei denen das jeweils nicht zutreffende Feld explizit auf never gesetzt wird.

Dieses Muster ist besonders nuetzlich bei generischen Bibliotheks Typen, die verhindern sollen, dass ein Konsument versehentlich beide Felder gleichzeitig setzt. Ein Objektliteral, das versucht, sowohl value als auch error mit einem konkreten Wert zu befuellen, wird vom Compiler abgelehnt, weil eines der beiden Felder in der jeweiligen Variante auf never typisiert ist und never per Definition keinen einzigen zuweisbaren Wert besitzt, nicht einmal undefined.

Ein weiteres praktisches Beispiel ist die Modellierung vollstaendig disjunkter Konfigurationsoptionen, etwa ein API Client, der entweder mit einem API Key oder mit einem OAuth Token authentifiziert werden kann, aber niemals mit beidem gleichzeitig. Eine Union aus zwei Objektvarianten, bei denen jeweils das nicht verwendete Feld auf never gesetzt wird, macht diese gegenseitige Ausschliesslichkeit zu einer vom Compiler durchgesetzten Regel, nicht nur zu einer in Kommentaren dokumentierten Konvention.


// Mutually exclusive fields enforced via never, not just optional properties
type CacheEntry<T> =
  | { status: "hit"; value: T; error?: never }
  | { status: "miss"; value?: never; error: string };

function readCache<T>(entry: CacheEntry<T>): T {
  if (entry.status === "hit") {
    return entry.value; // error is statically known to be never set here
  }
  throw new Error(entry.error);
}

// This assignment is rejected: value is typed as never in the "miss" branch
// const invalid: CacheEntry<number> = { status: "miss", value: 42, error: "x" };

type AuthConfig =
  | { method: "apiKey"; apiKey: string; oauthToken?: never }
  | { method: "oauth"; oauthToken: string; apiKey?: never };

function buildAuthHeader(config: AuthConfig): string {
  return config.method === "apiKey"
    ? `ApiKey ${config.apiKey}`
    : `Bearer ${config.oauthToken}`;
}

6. never versus void versus unknown: die Unterschiede

Die Abgrenzung zwischen never, void und unknown ist eine der haeufigsten Verwechslungen im TypeScript Typsystem, obwohl die drei Typen fundamental unterschiedliche Rollen spielen. void beschreibt eine Funktion, die zwar normal zurueckkehrt, deren Rueckgabewert aber nicht genutzt werden soll, meist weil sie nur eine Nebenwirkung ausfuehrt. Ein Wert vom Typ void ist typischerweise undefined, auch wenn TypeScript das nicht strikt erzwingt.

never hingegen beschreibt eine Funktion, die den Kontrollfluss ueberhaupt nicht bis zu einem Rueckgabepunkt fortsetzt, sei es durch Werfen, eine Endlosschleife oder einen Prozessabbruch. Der entscheidende praktische Unterschied: Code nach einem Aufruf einer void Funktion ist normal erreichbar, waehrend Code nach einem Aufruf einer never Funktion vom Compiler als unerreichbar markiert wird, was direkte Auswirkungen auf Narrowing und Exhaustiveness Checking hat.

unknown ist das genaue Gegenteil von never: der Top Type, dem jeder beliebige Wert zugewiesen werden kann, aber von dem aus ohne vorherige Typpruefung nichts Spezifisches getan werden darf. Waehrend never keinen einzigen Wert zulaesst, laesst unknown jeden Wert zu, verlangt aber explizite Narrowing Schritte, bevor der Wert genutzt werden kann. Diese Gegensaetzlichkeit macht die beiden Typen zu den beiden Extremen der TypeScript Typhierarchie, mit allen anderen Typen dazwischen.

7. never in generischen Utility Types nutzen

Neben den bereits erwaehnten eingebauten Utility Types wie Exclude und NonNullable lohnt es sich, eigene Utility Types zu entwerfen, die den never Type gezielt einsetzen, um bestimmte Kombinationen von Feldern strukturell zu verbieten. Ein bekanntes Muster ist ein XOR Utility Type, der zwei Objekttypen so kombiniert, dass Felder des einen Typs, die nicht im anderen existieren, im jeweils anderen Zweig auf never gesetzt werden, aehnlich dem AuthConfig Beispiel aus dem vorherigen Abschnitt, aber generisch fuer beliebige Objekttypen formuliert.

Ein weiteres nuetzliches Muster ist ein Impossible<T> Utility Type, der alle Schluessel eines Typs T auf never abbildet und als optionale Felder markiert. Kombiniert mit einer Intersection kann dieser Typ genutzt werden, um explizit zu verbieten, dass bestimmte Felder aus einem anderen Typ in einem konkreten Kontext gesetzt werden, ein Muster, das insbesondere bei generischen Formularbibliotheken oder API Client Typen nuetzlich ist, die versehentliche Feldueberschneidungen verhindern sollen.

Diese Utility Types mit never Feldern haben einen wichtigen praktischen Vorteil gegenueber Laufzeit Validierung: Der Fehler wird bereits waehrend der Entwicklung im Editor sichtbar, nicht erst beim Ausfuehren eines Tests oder in Produktion. Fuer Teams, die viel mit generischen, wiederverwendbaren Typen arbeiten, zahlt sich diese fruehe Fehlererkennung durch den never Type deutlich aus.


// A reusable XOR utility built on top of never
type XOR<A, B> =
  | (A & { [K in keyof B]?: never })
  | (B & { [K in keyof A]?: never });

interface EmailContact {
  email: string;
}
interface PhoneContact {
  phone: string;
}

type Contact = XOR<EmailContact, PhoneContact>;

const byEmail: Contact = { email: "team@mironsoft.de" }; // valid
const byPhone: Contact = { phone: "+49 123 456" }; // valid
// const invalid: Contact = { email: "a@b.de", phone: "+49 1" }; // rejected

8. Typische Fehler im Umgang mit never

Der haeufigste Fehler ist, im default Zweig eines switch Statements keinen echten Exhaustiveness Check einzubauen, sondern stattdessen stillschweigend undefined oder einen generischen Fallback Wert zurueckzugeben. Ohne den never typisierten Aufruf faellt eine neu hinzugefuegte, noch unbehandelte Variante niemals zur Compile Zeit auf, sondern erst, wenn sie in Produktion tatsaechlich den Fallback Pfad durchlaeuft, oft mit einem stillen, schwer nachvollziehbaren Bug als Folge.


// WRONG: silent fallback hides missing cases at compile time
function badArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
    default:
      return 0; // a new Shape variant silently returns 0, no compile error
  }
}

// WRONG: typing the assertUnreachable parameter as unknown defeats the check
function looseAssert(value: unknown): never {
  throw new Error(`Unhandled: ${JSON.stringify(value)}`); // always compiles
}

// RIGHT: never as the parameter type makes the check actually effective
function assertUnreachable(value: never): never {
  throw new Error(`Unhandled: ${JSON.stringify(value)}`);
}

Ein zweiter verbreiteter Fehler ist, never als Rueckgabetyp fuer Funktionen zu verwenden, die in Wirklichkeit unter bestimmten Bedingungen doch normal zurueckkehren koennen, etwa eine Funktion mit einem try/catch Block, bei dem der catch Zweig faelschlicherweise als unerreichbar angenommen wird. TypeScript erzwingt in solchen Faellen einen Fehler, weil ein tatsaechlich erreichbarer Codepfad existiert, der nicht mit never vereinbar ist, was in der Praxis meist ein Hinweis auf eine fehlerhafte Kontrollfluss Annahme ist, nicht ein TypeScript Fehler.

9. never im Vergleich zu anderen Bottom Types

Die folgende Tabelle stellt never den verwandten Typen void, unknown und any gegenueber, um die jeweiligen Anwendungsfaelle klar abzugrenzen.

Typ Zulaessige Werte Typische Verwendung Kontrollfluss Effekt
never Keine Funktionen ohne Rueckkehr, Exhaustiveness Markiert Code danach als unerreichbar
void Praktisch nur undefined Funktionen mit Nebenwirkung, kein Rueckgabewert Kein Effekt, Code danach normal erreichbar
unknown Alle Werte Unsichere externe Daten, JSON.parse Ergebnis Erzwingt Narrowing vor Nutzung
any Alle Werte, ungeprueft Sollte vermieden werden Deaktiviert Typpruefung komplett

Der Kontrast zwischen never und any ist besonders lehrreich: never ist die staerkste, restriktivste Aussage, die das Typsystem treffen kann, waehrend any effektiv die Typpruefung an dieser Stelle vollstaendig deaktiviert. Ein durchdachter Einsatz von never an den richtigen Stellen, kombiniert mit dem konsequenten Vermeiden von any, macht den groessten Unterschied fuer die tatsaechliche Typsicherheit einer Codebasis aus.

Mironsoft

TypeScript Typsystem, Exhaustiveness Checking und Utility Types

Typsicherheit, die unmoegliche Zustaende wirklich ausschliesst?

Wir richten Exhaustiveness Checking mit never ein, entwerfen eigene Utility Types und pruefen bestehende Discriminated Unions auf luckenhafte Fallbehandlung.

Typ Audit

Bestehende switch Statements auf fehlendes Exhaustiveness Checking pruefen

Utility Types

Eigene, wiederverwendbare Utility Types mit never fuer euer Team entwerfen

Team Schulung

Workshops zu never, Conditional Types und dem TypeScript Typsystem

10. Zusammenfassung

Der never Type ist weit mehr als eine theoretische Randnotiz im TypeScript Typsystem. Als expliziter Rueckgabetyp markiert er Funktionen, die garantiert niemals normal zurueckkehren, und ermoeglicht dem Compiler, nachfolgenden Code als unerreichbar zu erkennen. In Conditional Types filtert never gezielt Unionsmitglieder heraus und bildet damit die Grundlage vieler eingebauter Utility Types wie Exclude und NonNullable.

Der praktisch wertvollste Anwendungsfall bleibt Exhaustiveness Checking: eine never typisierte Hilfsfunktion im default Zweig eines switch verwandelt eine vergessene Union Variante in einen Compile Zeit Fehler statt in einen stillen Laufzeit Bug. Kombiniert mit dem Einsatz von never als Sentinel fuer strukturell unmoegliche Feldkombinationen entsteht ein Typsystem, das unmoegliche Zustaende nicht nur dokumentiert, sondern aktiv durch den Compiler ausschliesst.

Der never Type in TypeScript, das Wichtigste auf einen Blick

Grundbedeutung

Der Bottom Type, dem theoretisch niemals ein Wert zugewiesen werden kann, das Gegenteil von unknown.

Exhaustiveness

Eine never typisierte Hilfsfunktion im default Zweig macht fehlende Union Faelle zu Compile Zeit Fehlern.

Conditional Types

never filtert Unionsmitglieder heraus, Grundlage von Exclude, NonNullable und eigenen Utility Types.

Abgrenzung

never bedeutet kein Rueckgabepunkt, void bedeutet kein sinnvoller Wert, unknown bedeutet jeder Wert moeglich.

11. FAQ: Der never Type in TypeScript

1Was bedeutet der never Type?
Der Bottom Type, eine Menge ohne einen einzigen zulaessigen Wert, das Gegenteil von unknown.
2never vs. void?
void kehrt normal zurueck ohne sinnvollen Wert, never erreicht ueberhaupt keinen Rueckgabepunkt.
3Wie funktioniert Exhaustiveness Checking?
Eine never typisierte Hilfsfunktion im default Zweig erkennt unbehandelte Union Varianten zur Compile Zeit.
4Warum nicht unknown statt never?
unknown akzeptiert jeden Restwert, nur never erzwingt echte Vollstaendigkeit.
5never in Conditional Types?
Eine Union mit never Mitglied verwirft dieses automatisch, Grundlage von Exclude und NonNullable.
6Kann never Felder ausschliessen?
Ja, optionale never Felder verhindern, dass zwei sich ausschliessende Felder gleichzeitig gesetzt werden.
7Ist never dasselbe wie any?
Nein, never ist die staerkste Einschraenkung, any deaktiviert die Typpruefung komplett.
8Leitet TypeScript never automatisch ab?
Ja, bei garantierten Endlosschleifen oder reinem Werfen, eine explizite Annotation dokumentiert die Absicht zusaetzlich.
9Warum schlaegt eine never Funktion manchmal fehl?
Wenn ein echter erreichbarer Codepfad existiert, der nicht mit never vereinbar ist.
10Wofuer eignet sich ein XOR Utility Type?
Fuer sich gegenseitig ausschliessende Konfigurationen, etwa API Key oder OAuth Token, niemals beides.