Tipos utilitarios de TypeScript: Partial, Pick, Omit, Record y más

TypeScript trae de serie una serie de tipos utilitarios: transforman un tipo que ya tienes en otro, sin copiarlo a mano. Si tu Usuario cambia, todo lo que derivaste de él cambia solo. Estos son los que se usan a diario.

Un tipo de partida y sus variantes

interface Usuario {
  id: number;
  nombre: string;
  correo: string;
  telefono?: string;
  creado: Date;
}

// Partial: todo opcional. Ideal para «cambia solo lo que venga».
function actualizar(u: Usuario, cambios: Partial<Omit<Usuario, "id" | "creado">>): Usuario {
  return { ...u, ...cambios };
}

// Pick: solo algunas propiedades. Lo que se enseña en una lista pública.
type UsuarioPublico = Pick<Usuario, "id" | "nombre">;

// Omit: todas menos algunas. Lo que manda el formulario de alta (el id y la fecha los pone el servidor).
type NuevoUsuario = Omit<Usuario, "id" | "creado">;

// Required: lo contrario de Partial.
type UsuarioCompleto = Required<Usuario>;

// Record: un objeto con claves conocidas y valores del mismo tipo.
type Rol = "admin" | "editor" | "lector";
const permisos: Record<Rol, string[]> = {
  admin: ["leer", "escribir", "borrar"],
  editor: ["leer", "escribir"],
  lector: ["leer"],
};

// Readonly: nadie puede modificarlo después.
const ana: Readonly<Usuario> = {
  id: 1, nombre: "Ana", correo: "ana@ejemplo.com", creado: new Date("2026-10-20T10:00:00Z"),
};

const nueva = actualizar(ana, { telefono: "55 1234 5678" });
const publico: UsuarioPublico = { id: nueva.id, nombre: nueva.nombre };
const alta: NuevoUsuario = { nombre: "Luis", correo: "luis@ejemplo.com" };

console.log(nueva);
console.log(publico, alta);
console.log(permisos.editor);

Ábrelo en el editor y cámbialo →

npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es usuario.ts
node usuario.ts
salida{
  id: 1,
  nombre: 'Ana',
  correo: 'ana@ejemplo.com',
  creado: 2026-10-20T10:00:00.000Z,
  telefono: '55 1234 5678'
}
{ id: 1, nombre: 'Ana' } { nombre: 'Luis', correo: 'luis@ejemplo.com' }
[ 'leer', 'escribir' ]
UtilitarioQué hacePara qué se usa
Partial<T>Todas las propiedades, opcionalesActualizar solo lo que venga
Required<T>Todas, obligatoriasUn objeto ya completo, sin huecos
Readonly<T>Todas, de solo lecturaConfiguración o datos que no deben cambiar
Pick<T, "a" | "b">Solo esas propiedadesLo que se enseña en público
Omit<T, "a" | "b">Todas menos esasFormularios de alta sin id ni fechas
Record<K, V>Un objeto con claves K y valores VTablas: permisos por rol, precios por talla

Pick se queda con los campos que nombras y Omit con todos menos esosPick y Omit: dos formas de recortar el mismo tipoUsuarioidnombrecorreotelefono?creadoPick<Usuario, "id" | "nombre">idnombresolo las que nombrasOmit<Usuario, "id" | "creado">nombrecorreotelefono?todas menos esasSi Usuario gana un campo, Omit lo incluye solo; Pick, no. Elige según lo que quieras que pase.

Pick elige campos; Omit los quita. Esquema: decodigo.com.

Lo que impiden

Los tipos derivados son tan estrictos como los escritos a mano. Este archivo comete cuatro errores, uno por utilitario:

interface Usuario {
  id: number;
  nombre: string;
  correo: string;
  telefono?: string;
  creado: Date;
}
type Rol = "admin" | "editor" | "lector";

const ana: Readonly<Usuario> = { id: 1, nombre: "Ana", correo: "ana@ejemplo.com", creado: new Date() };
ana.nombre = "Ana María";

const publico: Pick<Usuario, "id" | "nombre"> = { id: 1, nombre: "Ana", correo: "ana@ejemplo.com" };

const permisos: Record<Rol, string[]> = {
  admin: ["leer", "escribir", "borrar"],
  editor: ["leer", "escribir"],
};

const completo: Required<Usuario> = { id: 1, nombre: "Ana", correo: "ana@ejemplo.com", creado: new Date() };

Ábrelo en el editor y cámbialo →

npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es errores.ts
tscerrores.ts(11,5): error TS2540: No se puede asignar a "nombre" porque es una propiedad de solo lectura.
errores.ts(13,73): error TS2353: El literal de objeto solo puede especificar propiedades conocidas y 'correo' no existe en el tipo 'Pick<Usuario, "id" | "nombre">'.
errores.ts(15,7): error TS2741: La propiedad "lector" falta en el tipo "{ admin: string[]; editor: string[]; }", pero es obligatoria en el tipo "Record<Rol, string[]>".
errores.ts(20,7): error TS2741: La propiedad "telefono" falta en el tipo "{ id: number; nombre: string; correo: string; creado: Date; }", pero es obligatoria en el tipo "Required<Usuario>".

El tercero es el más útil en la práctica: si mañana añades el rol "auditor", cada Record<Rol, …> del programa dejará de compilar hasta que le des su valor. Nada se queda sin configurar por olvido.

Sacar tipos de una función

A veces el tipo ya está escrito, implícito, en una función. Estos utilitarios lo extraen en lugar de repetirlo:

// Utilitarios que sacan tipos de una función que ya existe.
async function buscarPedido(id: number, conDetalle: boolean) {
  return { id, total: 1299.99, moneda: "USD" as const, detalle: conDetalle ? ["Audífonos"] : [] };
}

type Args = Parameters<typeof buscarPedido>;            // [id: number, conDetalle: boolean]
type Devuelve = ReturnType<typeof buscarPedido>;        // Promise<{ ... }>
type Pedido = Awaited<ReturnType<typeof buscarPedido>>; // { id: number; total: number; ... }

const args: Args = [42, true];
const p: Pedido = await buscarPedido(...args);
console.log(p);

// NonNullable quita null y undefined de una unión.
type QuizaTexto = string | null | undefined;
const t: NonNullable<QuizaTexto> = "seguro que hay texto";
console.log(t.length);
npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es funciones.ts
node funciones.ts
salida{ id: 42, total: 1299.99, moneda: 'USD', detalle: [ 'Audífonos' ] }
20
UtilitarioDa
Parameters<typeof f>Los parámetros de f, como tupla
ReturnType<typeof f>Lo que devuelve f; con una función async, la promesa
Awaited<T>Lo que hay dentro de la promesa
NonNullable<T>T sin null ni undefined
Readonly es superficial. Readonly<Usuario> impide cambiar ana.nombre, pero si una propiedad es un objeto o un array, su contenido sí se puede modificar. Lo contamos en las rarezas de TypeScript.

Todo el código de esta página se comprobó con TypeScript 7.0.2 en modo estricto (--strict --locale es) y se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. Documentación oficial: Utility Types en el manual de TypeScript.