Problem Solved

TypeScript · Partial

Home › Courses › TypeScript › Problems › Partial
🔷 Problem · 5 min read

How to make all properties optional.
One type, every field.

You have a User interface with five required fields. Now you need a PATCH endpoint where the client can send any subset — one field, three fields, all five. You don't want a second interface. This is exactly what Partial is for. One line, every property optional, no duplication.

Short answer: Wrap your type in Partial — type Patch = Partial. Every property becomes optional. This is a mapped type: { [K in keyof T]?: T[K] }. For nested objects that also need every level optional, write a recursive DeepPartial. Partial is erase-at-compile-time — the output JavaScript is unchanged.

✅ Solution 1 — Partial (the built-in)

One type parameter, every property optional.

Partial is a built-in utility type. It takes a type and produces a new type with all the same keys, but each key marked as optional. TypeScript ships this — you don't have to define it. It's the standard way to express "a subset of fields from T."

partial.ts
// ─── The base type ───────────────────────────
interface User {
id: string;
name: string;
email: string;
age: number;
}

// ─── Partial — every property optional ───────
type UserPatch = Partial;
// { id?: string; name?: string; email?: string; age?: number }

// ─── Any subset is valid ─────────────────────
const p1: UserPatch = {}; // ✅ all empty
const p2: UserPatch = { name: 'Alice' }; // ✅ one field
const p3: UserPatch = { email: 'a@x.com', age: 28 }; // ✅ two fields
const p4: UserPatch = { // ✅ all fields
id: '1', name: 'Alice', email: 'a@x.com', age: 28
};

// ─── Wrong types still rejected ──────────────
// const bad: UserPatch = { age: 'not a number' }; // ❌ type mismatch

// ─── The PATCH pattern ───────────────────────
function updateUser(id: string, patch: UserPatch): void {
// Only apply fields the caller provided
console.log('PATCH /users/' + id, patch);
}

updateUser('1', { name: 'Alicia' }); // ✅ only name
updateUser('1', { age: 29, email: 'a@y.com' }); // ✅ two fields

// ─── How Partial is defined ──────────────────
// This is the actual source:
//
// type Partial = { [K in keyof T]?: T[K] };
//
// A mapped type — iterates over keys, adds ? to each.

console.log(p2, p3, p4);
// Partial erased. The interface gone.

const p1 = {};
const p2 = { name: 'Alice' };
const p3 = { email: 'a@x.com', age: 28 };
const p4 = {
id: '1', name: 'Alice', email: 'a@x.com', age: 28
};

function updateUser(id, patch) {
console.log('PATCH /users/' + id, patch);
}

updateUser('1', { name: 'Alicia' });
updateUser('1', { age: 29, email: 'a@y.com' });

console.log(p2, p3, p4);

All keys, all optional

Partial keeps every key but adds ?. The result accepts any subset — including empty {}.

Values still typed

Optional doesn't mean untyped. age?: number means if it's present, it's a number. Passing a string is still an error.

The PATCH use case

Update endpoints accept "any subset of fields." That's exactly Partial. The client sends what changed; the server merges.

Compose with other utilities

Partial> — the user can update only name and email, and only one at a time. Full composition.

Partial is the right type for PATCH payloads and form drafts. Every other use is a smell. A common mistake: using Partial for a function that returns a user. That's wrong — a returned user has all its fields. Partial is for partial updates, not partial entities. If your code has a function that sometimes returns a partial user, split it into two functions: getUser(): User and findUser(): User | null. Partial should appear in exactly two places: request payloads and in-progress form state.

✅ Solution 2 — DeepPartial (recursive)

When nested objects also need to be partially updatable.

Standard Partial is shallow — it only affects the top level. Partial<{ user: { name: string } }> makes user optional, but if user is present, it still requires name. For nested updates, you need a DeepPartial — a recursive mapped type that walks into every object.

deep-partial.ts
// ─── Shallow Partial — only the top level ────
interface Profile {
name: string;
address: {
street: string;
city: string;
};
}

type ShallowPatch = Partial;
// { name?: string; address?: { street: string; city: string } }
// ⚠️ address is optional — but if present, requires BOTH street and city

const sp1: ShallowPatch = { name: 'Alice' }; // ✅
const sp2: ShallowPatch = { address: { street: 'Main', city: 'X' } }; // ✅ full address
// const sp3: ShallowPatch = { address: { city: 'X' } };
// ❌ Property 'street' is missing

// ─── DeepPartial — recursive ─────────────────
type DeepPartial = {
[K in keyof T]?: T[K] extends object ? DeepPartial : T[K]
};

type DeepPatch = DeepPartial;
// { name?: string; address?: { street?: string; city?: string } }

const dp1: DeepPatch = { name: 'Alice' }; // ✅
const dp2: DeepPatch = { address: { city: 'X' } }; // ✅ street optional
const dp3: DeepPatch = { address: {} }; // ✅ empty nested
const dp4: DeepPatch = {}; // ✅

// ─── The real-world merge function ──────────
function mergeDeep(base: T, patch: DeepPartial): T {
if (typeof base !== 'object' || base === null) return base;
const result = { ...base } as Record<string, unknown>;
for (const k in patch) {
const v = patch[k];
if (v !== undefined) {
if (typeof v === 'object' && v !== null) {
result[k] = mergeDeep(result[k], v as DeepPartial<unknown>);
} else {
result[k] = v;
}
}
}
return result as T;
}

const base: Profile = {
name: 'Alice',
address: { street: 'Main St', city: 'Springfield' }
};
const updated = mergeDeep(base, { address: { city: 'Shelbyville' } });
console.log(updated);
// { name: 'Alice', address: { street: 'Main St', city: 'Shelbyville' } }
const sp1 = { name: 'Alice' };
const sp2 = { address: { street: 'Main', city: 'X' } };

const dp1 = { name: 'Alice' };
const dp2 = { address: { city: 'X' } };
const dp3 = { address: {} };
const dp4 = {};

function mergeDeep(base, patch) {
if (typeof base !== 'object' || base === null) return base;
const result = { ...base };
for (const k in patch) {
const v = patch[k];
if (v !== undefined) {
if (typeof v === 'object' && v !== null) {
result[k] = mergeDeep(result[k], v);
} else {
result[k] = v;
}
}
}
return result;
}

const base = {
name: 'Alice',
address: { street: 'Main St', city: 'Springfield' }
};
const updated = mergeDeep(base, { address: { city: 'Shelbyville' } });
console.log(updated);

Partial is shallow

Partial only affects the top level. Nested objects still require all their fields if present. That's usually what you want.

DeepPartial recurses

DeepPartial walks into every nested object. Every level becomes optional. Use only for recursive update operations.

The conditional guard

T[K] extends object ? DeepPartial : T[K] — only recurse into objects. Primitives (string, number, Date) stay as-is.

Deep merge is a runtime concern

DeepPartial only types the input. The merge logic is real code — you need a mergeDeep function to apply it. Don't expect Object.assign to do this.

DeepPartial is dangerous — use it sparingly. It silently allows deeply incomplete objects that will crash at runtime when code assumes they're complete. A DeepPartial can be { address: {} } — no name, no street, no city. If your code does user.address.street.toUpperCase(), it fails. Fix: use DeepPartial only at update boundaries (incoming PATCH bodies), validate or merge immediately, and never use DeepPartial for data you'll pass around internally. Shallow Partial is much safer.

✅ Solution 3 — Compose with Pick and Omit

Partial is more powerful when combined with key filtering.

Sometimes you want "any subset of these specific fields." Combine Partial with Pick or Omit to restrict both the keys and the optionality. This is how real APIs are typed — you don't let a PATCH client update id or createdAt.

compose.ts
interface User {
id: string;
name: string;
email: string;
password: string;
createdAt: Date;
}

// ─── Partial + Omit: any subset of safe fields ─
type UserUpdate = Partial<Omit'id' | 'createdAt'>>;
// { name?: string; email?: string; password?: string }
// Client can send any subset — but not id or createdAt.

function updateUser(id: string, patch: UserUpdate): void {
console.log('PATCH', id, patch);
}

updateUser('1', { name: 'Alicia' }); // ✅
updateUser('1', { email: 'a@y.com' }); // ✅
// updateUser('1', { id: '2' }); // ❌ 'id' not in UserUpdate
// updateUser('1', { createdAt: new Date() }); // ❌ 'createdAt' not allowed

// ─── Partial + Pick: only specific fields ────
type ContactUpdate = Partial<Pick'email' | 'name'>>;
// { email?: string; name?: string }

function updateContact(id: string, patch: ContactUpdate): void {
console.log('PATCH contact', id, patch);
}

updateContact('1', { name: 'Alicia' }); // ✅
updateContact('1', { email: 'a@y.com' }); // ✅
// updateContact('1', { password: 'hack' }); // ❌ not allowed

// ─── Required + Partial: specific fields ─────
// "All fields optional EXCEPT id" — useful for
// update operations that require an identifier.
type PatchWithId = Partial & Pick'id'>;
// { id: string; name?: string; email?: string; password?: string; createdAt?: Date }

const wp: PatchWithId = { id: '1', name: 'Alice' }; // ✅
// const bad: PatchWithId = { name: 'Alice' }; // ❌ id required

// ─── The API endpoint pattern ────────────────
// This is how production APIs type PATCH bodies:
//
// type PatchBody =
// Partial>;
//
// type UserPatch = PatchBody;

console.log('composed patches ready');
function updateUser(id, patch) {
console.log('PATCH', id, patch);
}

updateUser('1', { name: 'Alicia' });
updateUser('1', { email: 'a@y.com' });

function updateContact(id, patch) {
console.log('PATCH contact', id, patch);
}

updateContact('1', { name: 'Alicia' });
updateContact('1', { email: 'a@y.com' });

const wp = { id: '1', name: 'Alice' };

console.log('composed patches ready');

Partial>

"Any subset of fields except K." The standard pattern for PATCH bodies — clients can update anything except immutable fields.

Partial>

"Any subset of only these fields." Restrict what the client can touch — name and email, but not password.

Partial & Pick

"Everything optional except these fields." Require an identifier while allowing partial updates.

Reusable helper

type PatchBody = Partial> — one type parameterised over the immutable keys, reused across every entity.

⚠️ Common mistakes

The Partial bugs that cost real time.

1. Using Partial for return types

A function that "sometimes returns a partial user" is a design smell. Fix: return User | null or split into two functions. Partial is for input.

2. Using Partial to skip field validation

Making every field optional because you don't want to enforce them. Fix: only optional when genuinely optional. Otherwise require it.

3. Assuming Partial is deep

Partial<{ address: { street: string } }> still requires street if address is present. Fix: DeepPartial for recursive updates.

4. Using DeepPartial everywhere

DeepPartial silently allows impossible objects. Fix: shallow Partial for 90% of cases; DeepPartial only at update boundaries.

5. Forgetting undefined vs missing

patch.name = undefined and omitting name are both valid, but they behave differently in merges. Fix: filter undefined in merge logic.

6. Not omitting immutable fields

If id and createdAt are in the Partial, the client can "update" them. Fix: Partial>.

7. Using Partial to represent "empty"

An "empty user" isn't a user — it's a form state. Fix: model form state as { values: Partial; errors: ... }, not a Partial directly.

8. Confusing Partial with nullable

Partial makes fields optional; it doesn't make them nullable. name?: string is not name: string | null. Fix: combine with | null only when null is a valid value.

❓ Frequently asked

How do I make all properties optional in TypeScript?

Wrap your type in Partial: type PartialUser = Partial. Every property becomes optional. Partial is a utility type that iterates over the keys with a mapped type: { [K in keyof T]?: T[K] }. The result has the same keys, each marked with ?, so the property can be missing or undefined.

What is the difference between Partial and optional properties?

Optional properties are declared with ? in the interface (name?: string). Partial applies ? to every property automatically. Use optional for a specific field that's genuinely sometimes missing; use Partial when you have a base type and want a version where all fields are optional.

What is DeepPartial in TypeScript?

DeepPartial makes every property optional recursively — nested objects also have their properties optional. Standard Partial is shallow: it only affects the top level. Use it for recursive config updates; avoid it for most code because it silently allows deeply incomplete objects.

When should I NOT use Partial?

Don't use Partial as a shortcut when the type actually has required fields — you'll lose compile-time guarantees. Don't use it for API response types. Don't use it for props interfaces where every prop really is required. Partial is right for: PATCH payloads, form drafts, and updates.

🔗 Related

← All TypeScript problems
Problem solved · TypeScript