Skip to content
TypeScript ts utility-types 3 min read

Partial, Required & Readonly

Partial, Required, and Readonly are the three foundational utility types for reshaping the modifiers on an object’s properties. Each takes an object type and returns a new type with every property flipped to optional, mandatory, or immutable respectively. They are implemented as simple homomorphic mapped types, so they preserve the original keys while toggling a single modifier. You will reach for them constantly when building update payloads, normalizing partial input, or locking down configuration objects.

Partial: every property optional

Partial<T> produces a copy of T where each property is marked optional (?). It is the workhorse for “patch” or “update” objects, where a caller supplies only the fields they want to change.

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

function updateUser(id: number, changes: Partial<User>): void {
  // changes can be {}, { name } , { email, name }, etc.
}

updateUser(1, { name: "Ada" });        // ✅ ok
updateUser(1, {});                     // ✅ ok
updateUser(1, { role: "admin" });      // ❌ Error: 'role' does not exist on Partial<User>

Every key of User becomes optional, but no new keys are allowed — the shape is still constrained to User’s keys. This is exactly what you want for a merge-style update.

Required: every property mandatory

Required<T> is the inverse of Partial: it strips the optional modifier from every property, making them all required. It also removes undefined from the property type when exactOptionalPropertyTypes is off.

interface Config {
  host?: string;
  port?: number;
  secure?: boolean;
}

function startServer(config: Required<Config>): void {
  // host, port, and secure are all guaranteed present here
  console.log(`${config.secure ? "https" : "http"}://${config.host}:${config.port}`);
}

// ❌ Error: missing 'port' and 'secure'
startServer({ host: "localhost" });

This is useful after you merge user-supplied options with defaults: the resolved object satisfies Required<Config>, signalling downstream code that nothing can be missing.

Readonly: every property immutable

Readonly<T> marks each property with the readonly modifier, so reassignment is a compile error. Note that this is shallow — nested objects remain mutable.

interface Point {
  x: number;
  y: number;
}

const origin: Readonly<Point> = { x: 0, y: 0 };
origin.x = 10; // ❌ Error: Cannot assign to 'x' because it is a read-only property

Readonly is a compile-time guarantee only. It disappears after compilation and does not call Object.freeze. For runtime immutability, use Object.freeze (and as const for literal values).

How they are built

All three are tiny homomorphic mapped types. The ? and readonly modifiers can be added with + or removed with -:

type MyPartial<T>  = { [K in keyof T]?: T[K] };
type MyRequired<T> = { [K in keyof T]-?: T[K] };   // -? strips optional
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

Because they iterate over keyof T, they automatically track changes to the source type. Understanding these definitions makes it easy to compose your own variants, such as a deep version.

Comparison at a glance

UtilityModifier changeTypical use case
Partial<T>adds ? to all propsupdate / patch payloads
Required<T>removes ? from all propsresolved config after defaults
Readonly<T>adds readonly to all propsimmutable constants, props

Building a deep variant

The built-ins are shallow. For a recursive version, combine a mapped type with a conditional check — see recursive types:

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

const settings: DeepReadonly<{ db: { url: string } }> = { db: { url: "..." } };
settings.db.url = "x"; // ❌ Error: read-only

Best Practices

  • Use Partial<T> for update functions, but validate at runtime that at least one field is present if your API requires it.
  • Reach for Required<T> to express “all defaults have been applied” rather than annotating each field manually.
  • Remember Readonly<T> is shallow and compile-time only — pair it with Object.freeze or as const when you need runtime immutability.
  • Prefer readonly arrays (ReadonlyArray<T> or readonly T[]) for parameters you never mutate.
  • Compose utilities (Readonly<Partial<T>>) instead of writing bespoke mapped types when the built-ins suffice.
  • Write a DeepPartial/DeepReadonly helper once and reuse it rather than nesting the shallow versions.
  • Keep source interfaces minimal; these utilities derive everything else, so you avoid duplicated shapes.
Last updated June 29, 2026
Was this helpful?