Utility Types in TypeScript: Partial, Pick, Omit und Record

TypeScript bringt fertige Helfer-Typen mit, die dir viel Tipparbeit ersparen. Lerne, wie du mit Partial, Pick, Omit und Record bestehende Typen elegant umformst.

Teilen

Wenn du in TypeScript schon eine Weile mit interface und type arbeitest, kennst du das Problem: Du hast einen sauberen Typ definiert – und plötzlich brauchst du fast denselben Typ, nur mit optionalen Feldern, ohne ein bestimmtes Feld oder als reine Lesekopie. Statt alles von Hand neu zu schreiben, kannst du auf sogenannte Utility Types zurückgreifen. Das sind fertige, generische Helfer-Typen, die TypeScript direkt mitbringt. In diesem Beitrag schauen wir uns die wichtigsten davon an und wie du sie im Alltag einsetzt.

Was sind Utility Types?

Utility Types sind vordefinierte generische Typen, die aus einem bestehenden Typ einen neuen ableiten. Du musst nichts installieren – sie sind Teil von TypeScript. Der Vorteil: Deine Typen bleiben DRY (Don't Repeat Yourself). Änderst du das Original, passen sich alle abgeleiteten Typen automatisch an. Als Ausgangspunkt nutzen wir in den folgenden Beispielen diesen Typ:

interface User {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
}

Von diesem User leiten wir jetzt Schritt für Schritt neue Varianten ab.

Partial und Required

Mit Partial<T> werden alle Felder eines Typs optional. Das ist ideal für Update-Funktionen, bei denen der Aufrufer nur die Felder mitgibt, die sich ändern sollen. Das Gegenstück ist Required<T>, das alle optionalen Felder verpflichtend macht.

// Alle Felder optional
type UserUpdate = Partial<User>;

function updateUser(id: number, changes: Partial<User>) {
  // changes darf { name } oder { email } oder beides enthalten
  console.log(`Aktualisiere User ${id}`, changes);
}

updateUser(1, { name: "Anna" });        // gültig
updateUser(1, { email: "a@test.de" });  // ebenfalls gültig

Ohne Partial müsstest du beim Aufruf immer alle vier Felder angeben – hier reicht genau das, was du wirklich ändern willst.

Pick und Omit

Oft brauchst du nur einen Ausschnitt eines Typs. Pick<T, Keys> wählt gezielt bestimmte Felder aus, Omit<T, Keys> entfernt sie. Ein klassischer Anwendungsfall: Du willst einen User nach außen geben, aber niemals das Passwort-Hash mitschicken.

// Nur id und name behalten
type PublicUser = Pick<User, "id" | "name">;
// entspricht: { id: number; name: string; }

// Alles außer passwordHash
type SafeUser = Omit<User, "passwordHash">;
// entspricht: { id: number; name: string; email: string; }

const profile: SafeUser = {
  id: 1,
  name: "Anna",
  email: "anna@test.de",
};

Pick und Omit sind zwei Seiten derselben Medaille: Bei wenigen benötigten Feldern nimmst du Pick, bei wenigen zu entfernenden Feldern Omit.

Record für Objekt-Maps

Mit Record<Keys, Type> beschreibst du ein Objekt, dessen Schlüssel aus einer festen Menge stammen und dessen Werte alle denselben Typ haben. Das ersetzt umständliche Index-Signaturen und macht deinen Code sicherer, weil TypeScript prüft, ob du wirklich alle Schlüssel abgedeckt hast.

type Role = "admin" | "editor" | "viewer";

const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
};

// Vergisst du einen Rollen-Schlüssel, meckert der Compiler sofort
console.log(permissions.editor); // ["read", "write"]

So kannst du zum Beispiel Berechtigungen, Übersetzungen oder Konfigurationen typsicher als Nachschlagetabelle abbilden.

Readonly und ein praktisches Beispiel

Mit Readonly<T> markierst du alle Felder als unveränderlich – TypeScript verhindert dann versehentliche Zuweisungen. Richtig stark werden Utility Types, wenn du sie kombinierst. Beim Anlegen eines neuen Datensatzes kennst du oft die id und den Zeitstempel noch nicht – die vergibt erst der Server oder die Datenbank.

interface Product {
  id: number;
  name: string;
  price: number;
  createdAt: Date;
}

// Beim Erstellen ohne id und createdAt arbeiten
type NewProduct = Omit<Product, "id" | "createdAt">;

function createProduct(data: NewProduct): Product {
  return {
    id: Math.floor(Math.random() * 1000),
    createdAt: new Date(),
    ...data,
  };
}

const buch = createProduct({ name: "Clean Code", price: 29.99 });

// Als unveränderliche Kopie weitergeben
const gesichert: Readonly<Product> = buch;
// gesichert.price = 0; // Fehler: Feld ist schreibgeschützt

Die Funktion createProduct akzeptiert nur die Felder, die der Aufrufer wirklich liefern kann, und gibt einen vollständigen Product zurück. Änderst du irgendwann die Product-Struktur, ziehen NewProduct und alle Aufrufe automatisch mit.

Fazit

Utility Types nehmen dir viel wiederholte Tipparbeit ab und halten deine Typen konsistent. Partial und Required steuern, ob Felder optional sind, Pick und Omit schneiden Typen zurecht, Record beschreibt typsichere Objekt-Maps und Readonly schützt vor ungewollten Änderungen. Am besten lernst du sie, indem du beim nächsten "fast gleichen" Typ kurz innehältst und fragst: Gibt es dafür schon einen Helfer? Meistens lautet die Antwort ja – und dein Code wird dadurch kürzer, sicherer und wartbarer.