TypeScript Utility Types – Partial, Pick, Omit & Record
TypeScript Utility Types wie Partial, Pick, Omit und Record im echten Einsatz – verständlich erklärt, ohne Theorieoverload.
Wer TypeScript zum ersten Mal begegnet, stolpert irgendwann über Zeilen wie Partial<User> oder Pick<Config, 'theme' | 'lang'> – und googlet erstmal, was das überhaupt bedeutet. Keine Schande. Diese sogenannten Utility Types sind eine der mächtigsten Funktionen von TypeScript, aber sie werden in Tutorials oft mit so viel Theorie erschlagen, dass man danach noch verwirrter ist als vorher.
Hier kommt die entspannte Version – mit echten Beispielen, die man so oder so ähnlich im Alltag braucht.
Was sind Utility Types überhaupt?
TypeScript bringt eine Reihe von eingebauten Typen mit, die andere Typen transformieren. Statt jeden Typ von Hand zu schreiben, lässt man TypeScript die Arbeit machen.
Das Grundprinzip:
// Ausgangspunkt: ein normaler Typ
type User = {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
};
// Und jetzt: Variationen davon, ohne alles neu schreiben zu müssen
type PartialUser = Partial<User>; // alle Felder optional
type PublicUser = Omit<User, 'id'>; // id ausgeblendet
type NameAndEmail = Pick<User, 'name' | 'email'>; // nur diese zwei
Klingt erstmal abstrakt – wird aber sofort klarer mit konkreten Anwendungsfällen.
Partial – wenn nicht alles ausgefüllt sein muss
Partial<T> macht alle Felder eines Typs optional. Das braucht man ständig – zum Beispiel bei Formularen, die der Nutzer noch nicht vollständig ausgefüllt hat, oder bei Update-Funktionen, wo man nur einzelne Felder ändern will.
type User = {
name: string;
email: string;
bio: string;
};
// Ohne Partial müsstest du alle drei Felder übergeben
function updateUser(id: number, changes: Partial<User>) {
// changes kann { name: 'Neu' } sein – ohne email und bio
}
updateUser(1, { name: 'Bünyamin' }); // ✅ funktioniert
updateUser(1, { email: 'neu@mail.de' }); // ✅ auch
updateUser(1, { name: 'X', bio: 'Hallo' }); // ✅ auch
Was TypeScript daraus macht, sieht so aus:
// Partial<User> entspricht:
type PartialUser = {
name?: string;
email?: string;
bio?: string;
};
Das ? macht jedes Feld optional. Nichts Magisches – TypeScript erledigt das nur automatisch.
Pick – nur die Felder, die du brauchst
Pick<T, Keys> wählt gezielt einzelne Felder aus einem Typ aus. Praktisch, wenn du einen großen Typ hast, aber an einer Stelle nur einen Teil davon brauchst.
type User = {
id: number;
name: string;
email: string;
passwordHash: string;
role: 'admin' | 'user';
};
// Das darf ruhig nach außen:
type PublicProfile = Pick<User, 'id' | 'name'>;
// In einer API-Antwort:
function getPublicProfile(user: User): PublicProfile {
return { id: user.id, name: user.name };
}
Der Vorteil: Wenn du irgendwann id in userId umbenennst, wirft TypeScript direkt einen Fehler – statt dass du das Problem erst zur Laufzeit bemerkst.
Omit – alles außer…
Omit<T, Keys> ist das Gegenteil von Pick: Du gibst an, was du nicht haben willst – alles andere bleibt.
type User = {
id: number;
name: string;
email: string;
passwordHash: string;
};
// Für neue Nutzer gibt es noch keine id – und das Passwort kommt separat:
type NewUser = Omit<User, 'id' | 'passwordHash'>;
// NewUser entspricht:
// { name: string; email: string }
function createUser(data: NewUser) {
// id wird von der Datenbank generiert
// passwordHash kommt separat ins Hashing
}
Pick vs. Omit ist letztlich eine Frage der Lesbarkeit: Bei wenigen gewünschten Feldern → Pick. Bei wenigen unerwünschten Feldern → Omit.
Record – strukturierte Objekte mit festen Keys
Record<Keys, Value> ist für Objekte, bei denen du die möglichen Keys kennst und alle denselben Wert-Typ haben sollen.
type Status = 'active' | 'inactive' | 'pending';
// Ohne Record:
const labels: { active: string; inactive: string; pending: string } = {
active: 'Aktiv',
inactive: 'Inaktiv',
pending: 'Ausstehend',
};
// Mit Record – kürzer und genauso typsicher:
const labels: Record<Status, string> = {
active: 'Aktiv',
inactive: 'Inaktiv',
pending: 'Ausstehend',
};
Was sofort auffällt: Vergisst du einen Status, meckert TypeScript. Fügst du später 'archived' zu Status hinzu, bricht der Compiler an dieser Stelle – genau dort, wo du es haben willst.
// Nach der Erweiterung von Status:
type Status = 'active' | 'inactive' | 'pending' | 'archived';
const labels: Record<Status, string> = {
active: 'Aktiv',
inactive: 'Inaktiv',
pending: 'Ausstehend',
// TS-Fehler: 'archived' fehlt ← genau richtig so
};
Kombinieren – der echte Mehrwert
Der eigentliche Nutzen kommt, wenn man diese Typen kombiniert:
type User = {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
};
// Formular zum Bearbeiten: id und createdAt sind nicht änderbar
type EditableUser = Partial<Omit<User, 'id' | 'createdAt'>>;
// Entspricht:
// { name?: string; email?: string; role?: 'admin' | 'user' }
Drei Zeilen, kein Copy-Paste, und wenn sich User ändert, zieht EditableUser automatisch mit.
Kurze Übersicht
Partial<T> → alle Felder optional
Required<T> → alle Felder pflicht (Gegenteil von Partial)
Pick<T, Keys> → nur diese Felder behalten
Omit<T, Keys> → diese Felder weglassen
Record<Keys, V> → Objekt mit festen Keys, alle gleicher Typ
Readonly<T> → alle Felder unveränderbar
Das sind die sechs, die man am häufigsten braucht. Der Rest (ReturnType, Parameters, Awaited etc.) kommt mit der Zeit von selbst – wenn man weiß, dass sie existieren.
TypeScript wird oft als “JavaScript mit Bürokratie” abgetan. Utility Types zeigen das Gegenteil: Weniger Wiederholung, mehr Sicherheit, und Fehler die zur Compile-Zeit auffallen statt beim Nutzer. Das ist kein Aufwand – das ist Komfort.