Which TypeScript utility types do you know and what are they for?
Show answer — try answering out loud first
They're generic types built into TypeScript that transform existing types instead of duplicating them: Partial makes everything optional, Pick and Omit select or remove properties, Record builds key-value objects, and ReturnType extracts a function's return type.
The core idea: define your base type once and derive the variants. If the base type changes tomorrow, all the variants update on their own.
The most used ones:
Partial<T>: all properties become optional. Perfect for update functions where you only send the fields that change.Required<T>is the opposite: all required.Pick<T, K>: a new type with only the properties you choose.Omit<T, K>: a new type without the properties you specify. Typical: removeidandcreadoEnfor the creation form.Record<K, V>: an object whose keys areKand whose values areV. Very useful with unions of literals as keys.ReturnType<F>: extracts the type a function returns, without writing it by hand.
The signal to use them: when you're about to copy and paste a type while changing a couple of things.
interface Usuario {
id: number;
nombre: string;
email: string;
rol: "admin" | "usuario";
}
// Partial: update only some fields
function actualizarUsuario(id: number, cambios: Partial<Usuario>): void {
console.log(`Actualizando ${id}`, cambios);
}
actualizarUsuario(1, { nombre: "Ana María" }); // doesn't require the rest
// Pick: only what's needed for a profile card (nombre + email)
type TarjetaPerfil = Pick<Usuario, "nombre" | "email">;
// Omit: the sign-up form doesn't have an id yet
type NuevoUsuario = Omit<Usuario, "id">;
const alta: NuevoUsuario = { nombre: "Luis", email: "luis@mail.com", rol: "usuario" };
// Record: a typed dictionary of permissions by role
type Permisos = Record<Usuario["rol"], string[]>;
// If a role is missing or there's an extra key, it won't compile
const permisos: Permisos = { admin: ["leer", "borrar"], usuario: ["leer"] };
// ReturnType: reuse the type a function returns
function crearSesion(usuario: Usuario) {
return { token: "abc123", usuario, expira: Date.now() + 3600 };
}
// Sesion = { token: string; usuario: Usuario; expira: number }
type Sesion = ReturnType<typeof crearSesion>;
Duplicating the type by hand instead of deriving it:
// (uses the Usuario interface from the previous example)
// Duplicate: if Usuario changes, this silently gets out of sync
interface UsuarioEditableMal {
nombre?: string;
email?: string;
rol?: "admin" | "usuario";
}
// Derived: it updates on its own when Usuario changes
type UsuarioEditable = Partial<Omit<Usuario, "id">>;
The problem with the duplicate isn't writing it today, it's maintaining it tomorrow: someone adds a field to Usuario and the manual copy never finds out.
"Utility types are type transformers that TypeScript ships with so you don't duplicate definitions. The ones I use most:
Partialfor update functions where I only send the fields that change,Omitfor variants like 'user without id' in creation forms,Pickwhen a component only needs two or three properties,Recordfor typed dictionaries, andReturnTypeto extract what a function returns. The advantage is maintenance: I define the base type once and the variants update on their own when it changes."
With the Usuario interface from the example: 1) define VistaPublica with only nombre and rol; 2) define BorradorUsuario where everything is optional except that it doesn't include id; 3) define UsuariosPorEmail, a dictionary from string to Usuario.
See solution
interface Usuario {
id: number;
nombre: string;
email: string;
rol: "admin" | "usuario";
}
// 1) Only two properties
type VistaPublica = Pick<Usuario, "nombre" | "rol">;
// 2) Without id and everything optional: utility types can be combined
type BorradorUsuario = Partial<Omit<Usuario, "id">>;
// 3) Dictionary with string key -> Usuario
type UsuariosPorEmail = Record<string, Usuario>;
const vista: VistaPublica = { nombre: "Ana", rol: "admin" };
const borrador: BorradorUsuario = { nombre: "Luis" };