Ein Type-Alias ist nichts anderes als ein Name für einen bestehenden Typ — ein Lesezeichen im Typsystem, das sich an einer zentralen Stelle definieren und an beliebig vielen Stellen wiederverwenden lässt. Im Gegensatz zu interface ist type nicht auf Objektformen beschränkt: Unions, Tuples, Funktionstypen, Primitives und seit TypeScript 3.7 auch rekursive Strukturen wie JSON-Bäume passen problemlos in einen Alias. Wichtig zu verstehen: ein Alias erzeugt keinen neuen Typ — der Compiler löst ihn strukturell auf, und type UserId = string ist exakt austauschbar mit string. Wer echte Nominal-Typisierung simulieren will, greift zu Branded Types, die einen Phantom-Tag an einen Strukturtyp hängen. Dieser Artikel zeigt die Mechanik vom einfachen Alias bis zu generischen, rekursiven und gebrandeten Varianten.

Was Type-Aliase sind

Ein Type-Alias vergibt einen Namen für einen Typ-Ausdruck. Die Syntax startet mit dem Schlüsselwort type, gefolgt von einem Bezeichner, einem Gleichheitszeichen und dem zu benennenden Typ. Der Alias ist ein reines Compiler-Konstrukt — zur Laufzeit existiert er nicht und erzeugt keinen Code.

TypeScript Minimaler Type-Alias
type T_UserId = string;
type T_Coord = { x: number; y: number };
type T_Status = "ok" | "error" | "pending";

Anders als interface, das ausschließlich Objektformen beschreibt, darf rechts vom Gleichheitszeichen jede beliebige Typ-Konstruktion stehen: Primitive, Unions, Intersections, Tuples, Funktionstypen, Mapped Types, Conditional Types. Diese Breite macht den Alias zum universellen Werkzeug der Typkomposition.

Wichtig: ein Alias ist strukturell. TypeScript prüft beim Vergleich nicht den Namen, sondern die Form. T_UserId und string sind aus Compiler-Sicht identisch — ein Wert vom Typ string lässt sich ohne Cast einer Variable vom Typ T_UserId zuweisen und umgekehrt.

Basis-Syntax

Aliase eignen sich für Primitives, für Literal-Unions und für jede Mischung daraus. Sie machen aus kryptischen Typ-Ausdrücken sprechende Namen.

TypeScript Primitive und Unions aliasieren
type T_Email = string;
type T_Age = number;
type T_HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type T_Result = T_Success | T_Failure;

type T_Success = { ok: true; value: number };
type T_Failure = { ok: false; reason: string };

Hoisting funktioniert auch für Typen: T_Result darf vor seinen Bestandteilen stehen — der Compiler löst zirkuläre Referenzen im Rahmen der weiter unten beschriebenen Regeln auf.

TypeScript Funktions- und Tuple-Typen
type T_Handler = (event: string, payload: unknown) => void;
type T_Predicate<T> = (value: T) => boolean;
type T_RgbTuple = readonly [number, number, number];
type T_Pair<K, V> = readonly [K, V];

Diese Kategorien sind mit interface schlicht nicht ausdrückbar — sie sind die Domäne des Type-Alias.

Aliase für Objekt-Typen

Für reine Objektformen ist ein Type-Alias semantisch nahezu gleichwertig zu interface. Beide sind strukturell, beide erlauben optionale und readonly-Properties, beide sind in extends-Klauseln und Generics einsetzbar.

TypeScript Alias für ein Objekt
type T_User = {
    readonly id: number;
    name: string;
    email?: string;
};

const u: T_User = { id: 1, name: "Anna" };

Zwei sichtbare Unterschiede bleiben: interface unterstützt Declaration Merging — derselbe Interface-Name darf mehrfach deklariert werden und die Felder addieren sich. Bei type ist das ein Duplicate-Identifier-Fehler. Umgekehrt erlaubt type Unions und Intersections direkt, was interface nicht kann.

TypeScript Komposition via Intersection
type T_Identifiable = { id: number };
type T_Named = { name: string };

type T_Entity = T_Identifiable & T_Named;

const e: T_Entity = { id: 1, name: "Foo" };

Generische Type-Aliase

Type-Aliase nehmen Typ-Parameter entgegen — analog zu generischen Funktionen oder Klassen. Damit lassen sich Container-Strukturen einmal beschreiben und beliebig spezialisieren.

TypeScript Einfacher generischer Alias
type T_Box<T> = { value: T };

const a: T_Box<string> = { value: "hello" };
const b: T_Box<number[]> = { value: [1, 2, 3] };

Mehrere Type-Parameter und Defaults sind möglich. Eine extends-Constraint schränkt die zulässigen Typ-Argumente ein.

TypeScript Constraints und Defaults
type T_Result<TOk, TErr extends Error = Error> =
    | { ok: true; value: TOk }
    | { ok: false; error: TErr };

const r1: T_Result<number> = { ok: true, value: 42 };
const r2: T_Result<number, TypeError> = {
    ok: false,
    error: new TypeError("nope")
};

Generische Aliase sind das Fundament fast aller Utility-Types — Partial<T>, Pick<T, K> oder Record<K, V> sind nichts anderes als generische Type-Aliase mit Mapped-Type-Body.

TypeScript Eigener Utility-Type
type T_Nullable<T> = T | null;
type T_Maybe<T> = T | null | undefined;
type T_KeysOf<T> = keyof T;
type T_PromiseOr<T> = T | Promise<T>;

Rekursive Aliase

Seit TypeScript 3.7 darf ein Type-Alias sich selbst referenzieren — solange die Rekursion über einen Objekt- oder Array-Typ vermittelt wird. Direkter Self-Reference wie type T = T bleibt ein Fehler. Diese Lockerung erlaubt es, Baum- und JSON-Strukturen ohne Helfer-Interfaces auszudrücken.

TypeScript JSON-Wert als rekursiver Alias
type T_JsonValue =
    | string
    | number
    | boolean
    | null
    | T_JsonValue[]
    | { [key: string]: T_JsonValue };

const data: T_JsonValue = {
    name: "Anna",
    age: 30,
    roles: ["admin", "user"],
    meta: { active: true, score: null }
};

Vor TS 3.7 brauchte es einen Umweg über ein leeres Interface — heute gehört dieser Trick der Vergangenheit an.

TypeScript Tree-Struktur
type T_TreeNode<T> = {
    value: T;
    children: T_TreeNode<T>[];
};

const tree: T_TreeNode<string> = {
    value: "root",
    children: [
        { value: "a", children: [] },
        { value: "b", children: [{ value: "b1", children: [] }] }
    ]
};

Wichtig: die Rekursion muss durch Objekt- oder Array-Indirection laufen. Eine direkte Union wie type T_X = string | T_X wird vom Compiler verweigert, weil die Auflösung nicht terminiert.

Aliase vs. interface

Die Unterschiede sind in der Praxis überschaubar — beide sind strukturell, beide unterstützen Generics, beide tauchen im Tooling saüber auf. Die Entscheidung fällt meist nach Stil-Präferenz und Use-Case.

Aspekttype-Aliasinterface
Objektformen++
Unions / Tuples / Primitives+
Declaration Merging+
extends direktüber &+
Mapped / Conditional Types+
Public-API-Erweiterbarkeit+

Faustregel: interface für exportierte, von Konsumenten erweiterbare Verträge; type für Komposition, Unions und alles, was kein reines Objekt ist. Eine vertiefte Gegenüberstellung steht im Artikel type vs. interface.

Branded Types

TypeScript ist structural — das bedeutet: type T_UserId = string und type T_OrderId = string sind aus Compiler-Sicht identisch und beliebig austauschbar. Für Domänen-Modelle ist das oft zu lax: man möchte verhindern, dass eine OrderId versehentlich dort landet, wo eine UserId hingehört. Die Lösung heißt Branded Types (auch „Nominal Types simulieren"): an einen Strukturtyp wird ein Phantom-Property gehängt, das nur im Typ existiert.

TypeScript Brand via Intersection
type T_UserId = string & { readonly __brand: "UserId" };
type T_OrderId = string & { readonly __brand: "OrderId" };

const u: T_UserId = "u_123" as T_UserId;
const o: T_OrderId = "o_456" as T_OrderId;

function loadUser(id: T_UserId): void { /* ... */ }

loadUser(u);                    // + ok
loadUser(o);                    // − Fehler: OrderId ist nicht UserId
loadUser("u_123");              // − Fehler: rohe Strings verboten

Das __brand-Feld existiert zur Laufzeit nicht — es ist ein reines Typ-Konstrukt. Beim Erzeugen ist eine as-Assertion unvermeidbar; sauberer als verstreute Casts ist eine Factory-Funktion, die Validierung und Branding kapselt.

TypeScript Factory mit Validierung
function makeUserId(raw: string): T_UserId {
    if (!/^u_[a-z0-9]+$/.test(raw)) {
        throw new Error(`Invalid UserId: ${raw}`);
    }
    return raw as T_UserId;
}

const id = makeUserId("u_42");
loadUser(id);                   // + saüber gebrandet

Branded Types sind ideal für IDs, Einheiten (Meter, Seconds), sanitisierte Strings (HtmlSafeString) oder validierte Werte. Sie kosten an der Konstruktionsstelle minimal Aufwand und schaffen dafür eine Typ-Grenze, die der Compiler durchsetzt.

Re-Export von Type-Aliasen

Type-Aliase lassen sich wie Werte exportieren — mit dem Unterschied, dass export type und import type rein typseitig sind und im emittierten JavaScript keinerlei Spur hinterlassen. Das ist besonders wichtig, wenn isolatedModules oder verbatimModuleSyntax aktiv sind, weil der Compiler dann nicht raten muss, welche Imports Werte und welche Typen sind.

TypeScript Type-only Export
// user.ts
export type T_User = {
    id: number;
    name: string;
};

export type T_UserRole = "admin" | "editor" | "viewer";
TypeScript Type-only Import
// consumer.ts
import type { T_User, T_UserRole } from "./user";

const u: T_User = { id: 1, name: "Anna" };
const r: T_UserRole = "admin";

Mit import type ist garantiert, dass kein Runtime-Import entsteht — der Bundler kann das Statement vollständig elidieren. Wer Werte und Typen aus demselben Modul importiert, kann inline annotieren.

TypeScript Inline type-Marker
import { createUser, type T_User } from "./user";

const u: T_User = createUser("Anna");

Seit TypeScript 5.0 erzwingt die Option verbatimModuleSyntax diesen expliziten Stil — jedes Typ-Import muss als type markiert sein, andernfalls bleibt es ein Werte-Import mit Runtime-Auswirkung.

Aliasing als Doku

Der praktische Mehrwert eines Type-Alias liegt oft weniger im Typsystem als in der Lesbarkeit. Ein wiederkehrender Typ-Ausdruck wie string | number | null verbraucht Aufmerksamkeit; ein Alias T_Scalar macht aus dem Rauschen einen Begriff.

TypeScript Vorher: Typ-Ausdruck dupliziert
function format(value: string | number | null): string { /* ... */ }
function parse(input: string): string | number | null { /* ... */ }
function isEmpty(v: string | number | null): boolean { /* ... */ }
TypeScript Nachher: ein Name, drei Stellen
type T_Scalar = string | number | null;

function format(value: T_Scalar): string { /* ... */ }
function parse(input: string): T_Scalar { /* ... */ }
function isEmpty(v: T_Scalar): boolean { /* ... */ }

Faustregel: wenn derselbe Typ-Ausdruck zweimal im Code vorkommt, bekommt er einen Namen. Aliase sind kostenlos zur Laufzeit, machen Refactoring trivial und liefern bei Fehlern eine sprechende Marke in der Compiler-Ausgabe.

Interessantes

Aliase erzeugen keinen neuen Typ

Ein Type-Alias ist reine Namensgebung — kein Wrapper, kein eigener Typ. type T_UserId = string ist strukturell identisch mit string, der Compiler vergleicht über die Form, nicht über den Namen. Wer Nominal-Typisierung will, braucht Branded Types.

Branded Types brauchen 'as' beim Konstruktor

Da das Phantom-Brand-Feld zur Laufzeit nicht existiert, lässt sich ein gebrandeter Wert nur per Assertion erzeugen. Kapsele diesen Cast in einer Factory-Funktion mit Validierung — so bleibt das as auf eine zentrale Stelle beschränkt.

Rekursive Aliase brauchen Indirection

Seit TS 3.7 stabil — aber nur, wenn die Selbst-Referenz über einen Objekt- oder Array-Typ läuft. type T_X = T_X | string bleibt ein Fehler; type T_X = string | T_X[] ist erlaubt.

type und interface sind beide strukturell

Trotz unterschiedlicher Syntax sieht der Compiler beide Konstrukte gleich an: kompatibel ist, was die gleiche Form hat. Der Unterschied liegt in Komposition, Merging und Erweiterbarkeit — nicht in der Identitätsregel.

import type / export type sind Runtime-frei

Beide Statements werden vom Compiler vollständig entfernt. Sie sind Pflicht, sobald nur Typen aus einem Modul gebraucht werden — der Bundler eliminiert sonst Side-Effect-Imports gegebenenfalls nicht.

Aliase für Funktions- und Tuple-Typen

Anders als interface kann type auch Funktions- und Tuple-Signaturen direkt benennen — type T_Handler = (e: Event) => void oder type T_Rgb = readonly [number, number, number]. Das macht Aliase zur natürlichen Wahl für Callback- und Tupel-Domänen.

Generics in Aliasen: Constraints und Defaults

Type-Parameter dürfen extends-Constraints und Default-Werte tragen — exakt wie bei generischen Funktionen. type T_Result<T, E extends Error = Error> beschränkt E auf Fehler-Typen und gibt einen Default vor.

verbatimModuleSyntax seit TS 5.0

Diese Option erzwingt, dass jeder Typ-Import explizit als import type markiert ist. Das macht Builds vorhersehbar — der Compiler entscheidet nicht mehr heuristisch, ob ein Import zur Laufzeit benötigt wird.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Type System

Zur Übersicht