Course outline · 0% complete

0/26 lessons0%

Course overview →

Partial, Pick, and Omit

lesson 7-1 · ~11 min · 21/26

Lesson 6-2 introduced Box<T>, an interface with a type slot. Built-in utility types such as Partial<T> are the same idea applied to whole types.

Partial<Settings> produces a type like Settings but with every property optional. It keeps every property of the original and marks each one with ?, the same optional marker from lesson 3-2.

Two points worth fixing in place now. Utility types are generic types that take another type as their argument, so the thing in the angle brackets is a type rather than a value. And they are purely type-level tools, so nothing about them exists at runtime.

Types made from other types

TypeScript ships utility types, generic types that transform existing ones. They exist because real projects constantly need variations of one shape, such as the same user with fields optional for an update form, or with private fields removed for display. Hand-writing each variation means shapes that drift out of sync.

The ones you will use weekly:

UtilityMeaning
Partial<T>every property of T becomes optional
Pick<T, K>keep only the listed properties
Omit<T, K>keep everything except the listed properties
Readonly<T>every property of T becomes readonly

The classic Partial use case is an update function. Callers change one setting, not all three:

interface Settings {
  theme: string;
  fontSize: number;
  autosave: boolean;
}

function applyChanges(base: Settings, changes: Partial<Settings>): Settings {
  return { ...base, ...changes };
}

The spread syntax from Advanced JavaScript merges the objects, and the types guarantee changes only ever contains valid Settings properties.

Readonly<T> applies lesson 3-2's readonly to every property at once, for values that must never be mutated after creation.

User id: number name: string email: string passwordHash: string Partial<User> every field optional for update forms Pick<User, ...> only id and name for previews Omit<User, ...> everything but the password hash Add a field to User and all three derived types update themselves.
Partial, Pick, and Omit each derive a new type from one source, so the source stays the single place a shape is edited.

Applying a partial update

The changes object below supplies only theme, and everything else comes from the base settings.

interface Settings {
  theme: string;
  fontSize: number;
  autosave: boolean;
}

const defaults: Settings = { theme: "light", fontSize: 14, autosave: true };

function applyChanges(base: Settings, changes: Partial<Settings>): Settings {
  return { ...base, ...changes };
}

const mine = applyChanges(defaults, { theme: "dark" });
console.log(mine.theme + " " + mine.fontSize + " " + mine.autosave);

Output

dark 14 true

Partial<Settings> means each of the three properties may be present or absent, and no others are allowed. That second half is what makes the type useful rather than merely permissive.

Pass { theme: "dark", fontSizes: 16 } and the typo gets caught, because fontSizes is not a property of Settings and therefore not a property of Partial<Settings> either. Without the type, that misspelling would silently do nothing and the setting would appear not to save.

Pick and Omit: sub-shapes without duplication

The server behind a web API should never send passwordHash to the browser. Instead of writing a second interface by hand and keeping it in sync forever, derive it:

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

type PublicUser = Omit<User, "passwordHash">;
type UserPreview = Pick<User, "id" | "name">;

PublicUser has everything except the hash. UserPreview has only id and name, note the union of literal property names from Unit 4. When User gains a field, the derived types update themselves.

updateTask: a partial update that does not mutate

updateTask takes a task and a partial set of changes, then returns a new task with the changes merged in.

interface Task {
  title: string;
  done: boolean;
}

function updateTask(task: Task, changes: Partial<Task>): Task {
  return { ...task, ...changes };
}

const t: Task = { title: "study", done: false };
const finished = updateTask(t, { done: true });

console.log(finished.title + " done: " + finished.done);
console.log(t.title + " done: " + t.done);

Output

study done: true
study done: false

Reading the merge

  • Spreading task first and changes second means the changes win wherever both define a property. Reversing the order would make the update do nothing, since the original values would overwrite it.
  • The title survives untouched because changes never mentions it, which is exactly what Partial is for.
  • The second printed line proves the original object was not mutated. updateTask built a new one, which is the pattern React state updates and most modern data handling rely on.