Utility type partial in TypeScript
Partial<T> - a built-in TypeScript utility type that takes any object type T and makes every property optional.
Theory
TL;DR
- Think of it like a draft: fill only the fields you have, skip the rest
{ name: string }becomes{ name?: string }for every property in T- Use it for PATCH requests, form state, and config objects with optional fields
- It only goes one level deep. Nested objects stay required if you provide them
- No runtime cost. TypeScript removes it during compilation
Quick example
type User = {
id: number;
name: string;
email: string;
};
// All fields become optional
const update: Partial<User> = { name: "Alice" }; // id and email omitted - fine
// Merge pattern
const fullUser: User = { id: 1, name: "Bob", email: "bob@test.com" };
const updated: User = { ...fullUser, ...update };
// { id: 1, name: "Alice", email: "bob@test.com" }update carries only the field you want to change. Spread merges it back into a complete User.
Key difference
Partial<T> scales with T automatically. Add a field to User and every Partial<User> in the codebase reflects it without any extra work. That beats maintaining { name?: string; email?: string } by hand and keeping it in sync.
When to use
- PATCH endpoints: send only changed fields to the server
- React form state: track which fields the user has touched
- Config objects: set defaults and let callers override parts
- Reducer updates: merge a partial payload into existing state
How the compiler handles this
TypeScript expands Partial<T> as a mapped type: { [K in keyof T]?: T[K] }. The compiler walks every key in T and adds ?. Nothing ends up in the JavaScript output. V8 and the browser never see it.
Common mistakes
Mistake 1: assuming Partial works recursively.
type Nested = {
user: { name: string; age: number };
};
const x: Partial<Nested> = { user: { name: "Alice" } }; // Error! age is still requiredPartial only touches the top-level keys. If you provide user, its inner shape stays fully required. The nested-object trap catches almost everyone the first time. For deep optionality, write a DeepPartial:
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;Mistake 2: forgetting runtime checks for undefined.
function update(data: Partial<User>) {
console.log(data.id.toString()); // Compiles. Crashes if id is missing.
}TypeScript does not emit any guards. Add the check yourself: if (data.id !== undefined).
Mistake 3: using Partial when one field must always be present.
If id is always required, Partial<User> still allows it to be skipped. Combine types instead:
type PatchUser = Pick<User, "id"> & Partial<Omit<User, "id">>;Mistake 4: applying Partial to a primitive type.
type ID = number;
const x: Partial<ID> = undefined; // Error. Partial<number> is still number.Primitives have no keys. Partial has no effect on them. Use number | undefined instead.
Real-world usage
- React (React Hook Form):
useState<Partial<Profile>>({})for partial form state - NestJS:
Partial<CreateUserDto>as the body type for PATCH endpoints - Redux Toolkit:
Partial<State>in slice reducers for optional field updates - Prisma:
prisma.user.update({ data: partial })accepts a partial model shape
Follow-up questions
Q: What does Partial<{ a: string; b: number }> expand to?
A: { a?: string; b?: number }. Every property gets ? added.
Q: Does Partial preserve readonly modifiers?
A: No. Partial<{ readonly x: number }> drops readonly, leaving { x?: number } as writable.
Q: Any runtime performance impact?
A: None. Pure compile-time. The JavaScript output is identical with or without Partial.
Q: What is the difference between Partial<T> and { [K in keyof T]?: T[K] }?
A: They are identical. Partial<T> is a named alias for that mapped type, defined in TypeScript's lib.es5.d.ts.
Q: (Senior) How would you type a PATCH endpoint where id is always required but other fields are optional?
A: type PatchUser = Pick<User, 'id'> & Partial<Omit<User, 'id'>>. This enforces id while making everything else optional.
Examples
Basic: updating an object with spread
type Point = { x: number; y: number; z?: number };
function move(origin: Point, delta: Partial<Point>): Point {
return { ...origin, ...delta };
}
const start: Point = { x: 0, y: 0 };
const result = move(start, { x: 10 });
// { x: 10, y: 0 }delta carries only the axes to change. move merges it with the current position and returns a full Point. z is already optional in Point, so it never causes issues here.
Intermediate: form state in React
interface Profile {
name: string;
age: number;
bio: string;
}
function EditProfileForm({ user }: { user: Profile }) {
const [draft, setDraft] = useState<Partial<Profile>>({});
const handleSave = () => {
const updated: Profile = { ...user, ...draft }; // safe merge
saveProfile(updated);
};
return (
<input
value={draft.name ?? user.name}
onChange={(e) => setDraft((d) => ({ ...d, name: e.target.value }))}
/>
);
}draft holds only the fields the user has touched. On save, it merges over the original user so no field goes missing. This pattern works well with any form library or plain React state.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.