Skip to content
Theory · Type operators and utility types · lesson 7 of 8

Pick, Omit and Record

The generator can also make short cards from the full form. A list of users on the site needs only the id and the name, and a public profile page needs everything except the email. You could describe each card with an interface of its own, but then the fields are written in several places again. Change the type of a field in User, and you have to hunt down every copy.

The utility types Pick and Omit cut the part you need out of an existing type, and Record builds an object type from a list of keys. At the end comes ReturnType, which takes a function's result type.

Pick and Omit

Both take a type and names of its properties, but they do opposite things:

TypeScript
interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

type Card = Pick<User, "id" | "name">;
type PublicUser = Omit<User, "email">;

const card: Card = { id: 1, name: "Ann" };
const shown: PublicUser = { id: 1, name: "Ann", age: 30 };
console.log(card.name, shown.age); // Ann 30

Pick

Pick<User, "id" | "name"> keeps only the listed properties, which gives the type { id: number; name: string }. The names are passed as a union of literals, and TypeScript checks each one. You can name only a real property of User, and tsc answers Pick<User, "phone"> with an error:

TEXT
error TS2344: Type '"phone"' does not satisfy the constraint 'keyof User'.

The definition of Pick has the familiar constraint K extends keyof T.

Omit

Omit<User, "email"> does the opposite: it removes the listed properties and keeps all the others, id, name and age. It is handy when there is less to remove than to keep.

Both types follow the original. Change the type of name in User, and it changes in both Card and PublicUser. Interviewers like to ask how Pick differs from Omit.

Record

Record<K, V> builds an object from a list of keys: every key in K becomes a property whose value has the type V. That is a handy way to describe tables such as translations, where every language needs a string:

TypeScript
const greetings: Record<"ru" | "en", string> = {
    ru: "Привет",
    en: "Hello",
};
const stock: Record<string, number> = { apple: 5 };
console.log(greetings.en, stock["apple"]); // Hello 5

Let's walk through the example line by line:

  1. Record<"ru" | "en", string> is an object with the properties ru and en, both holding strings.
  2. Both properties are required: tsc checks that no language is forgotten.
  3. Record<string, number> has any string as a key and a number as a value. It is the same index signature as { [key: string]: number }, only written shorter.
  4. The program prints Hello 5.

If you forget a language, say by writing only { ru: "Привет" }, tsc names the missing key. In the message TypeScript 7 prints the keys sorted, so "en" comes first:

TEXT
error TS2741: Property 'en' is missing in type '{ ru: string; }' but required in type 'Record<"en" | "ru", string>'.

ReturnType: the result type

One more utility takes a type from a function. ReturnType<F> is the type of what a function of type F returns. The utility needs the type of the function, not the function itself, so the name is preceded by typeof:

TypeScript
function parseUser(line: string) {
    const parts = line.split(",");
    return { name: parts[0], age: Number(parts[1]) };
}

type ParsedUser = ReturnType<typeof parseUser>;

const p: ParsedUser = parseUser("Ann,30");
console.log(p.name, p.age); // Ann 30

Let's see where the type ParsedUser came from:

  1. For the result of parseUser TypeScript infers the type { name: string; age: number; }.
  2. typeof parseUser is the type of the function itself: (line: string) => { name: string; age: number; }.
  3. ReturnType takes the result type out of that type. You did not have to describe ParsedUser by hand.

If you forget typeof, tsc tells you what is missing:

TEXT
error TS2749: 'parseUser' refers to a value, but is being used as a type here. Did you mean 'typeof parseUser'?

Common mistake

A property name in Omit has a typo:

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

type Preview = Omit<User, "emial">;

const p: Preview = { id: 1, name: "Ann" }; // ✗
TEXT
error TS2741: Property 'email' is missing in type '{ id: number; name: string; }' but required in type 'Preview'.

tsc is silent on the line with Omit. Unlike Pick, Omit does not check the names: its constraint lets through any string, number or symbol, not only keyof T. User has no property emial, there was nothing to remove, and email stayed in Preview. The error surfaced later, at the object without an email. If tsc asks for a property you removed with Omit, check the name in the Omit itself.

Key points

  • Pick<User, "id" | "name"> keeps only the named properties, Omit<User, "email"> keeps all but the named ones.
  • Record<"ru" | "en", string> is an object where every key of the union is required and holds a string.
  • ReturnType<typeof f> is the result type of the function f; typeof is needed because the utility takes a type, not a value.
  • Pick checks property names, and Omit does not: a typo in Omit passes silently.