Partial<Type>
Constructs a type with every property of Type made optional, for patches and partial updates.
type Partial<Type> = { [Property in keyof Type]?: Type[Property] }- Added in TypeScript 2.1
Parameters
| Parameter | Type | Description |
|---|---|---|
$Type | any type | The source type whose properties are copied and made optional. Non-object types pass through the mapping unchanged. |
Return value
An object type with the same properties as Type, each marked optionalA mapped type over keyof Type where the `?` modifier has been added to every property.
Errors
Nothing at runtime; the type is erased. The checker errors only if you assign an object with excess or wrongly-typed properties.
Changelog
- PHP 4.4Under `exactOptionalPropertyTypes`, an optional property produced by Partial no longer accepts an explicit `undefined` — `{ fontSize: undefined }` stops being assignable.
Partial is a mapped type that copies every property of Type and adds the ? modifier. It is the type you reach for whenever a function accepts some of an object's fields rather than all of them.
When to reach for it
- An update or patch function:
update(id: string, changes: Partial<User>). - An options bag where every option has a default supplied by the callee.
- Building a value incrementally before validating it into the full type.
What trips people up
It is one level deep. Partial<Config> makes the top-level keys optional but leaves nested objects fully required, so { db: { host: "x" } } still fails if db has other required fields. There is no built-in deep variant; you write your own recursive mapped type.
Optional is not the same as absent. Spreading a patch that contains an explicit undefined overwrites the base value with undefined at runtime, even though the types looked fine. Filter undefined keys out before merging, or enable exactOptionalPropertyTypes so the checker rejects them.
It widens more than you think. Partial<T> makes every key optional, including the ones that identify the record. A patch type usually wants Pick for the identity fields intersected with Partial for the rest.
Example
When to use it
- Type the `changes` argument of a repository or store update method.
- Accept a caller-supplied options object where the function fills in every default.
- Model a PATCH request body derived from an existing resource interface.
Frequently asked questions
How do I make all properties optional in TypeScript?
Wrap the type in `Partial<T>` — it maps over `keyof T` and adds the `?` modifier to every property. It was added in TypeScript 2.1 and is built in, so no import is needed.
Why does Partial not make nested properties optional?
Because it is a single-level mapped type: it adds `?` to the top-level keys and copies each property's type verbatim, nested objects included. For a recursive version you write your own, e.g. `type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }`.
What is the opposite of Partial in TypeScript?
`Required<T>` is the opposite — it removes the `?` modifier from every property using the `-?` mapping modifier. Partial adds optionality, Required strips it.
More examples
Keep the identity field required
The output is the full user record with `active` flipped to false and every other field untouched. Because `id` comes from `Pick` rather than `Partial`, omitting it is a compile error — which is what you want, since a patch with no identity has nothing to apply to.
interface User {
id: string;
name: string;
email: string;
active: boolean;
}
// id must be present; everything else is optional
type UserPatch = Pick<User, "id"> & Partial<Omit<User, "id">>;
const store = new Map<string, User>();
store.set("u1", { id: "u1", name: "Ada", email: "[email protected]", active: true });
function update(patch: UserPatch): User {
const current = store.get(patch.id);
if (!current) throw new Error(`no user ${patch.id}`);
const next = { ...current, ...patch };
store.set(patch.id, next);
return next;
}
console.log(update({ id: "u1", active: false }));The undefined trap when merging
The first log shows `bio` clobbered to `undefined` — the spread copied the key because it exists, undefined value and all. The second log shows the same merge after stripping undefined entries, preserving "compiler author". The type checker flags neither version; only `exactOptionalPropertyTypes` would reject the explicit undefined.
interface Profile {
name: string;
bio: string;
}
const current: Profile = { name: "Grace", bio: "compiler author" };
// A form serialiser produced an explicit undefined for an untouched field
const patch: Partial<Profile> = { name: "Grace Hopper", bio: undefined };
console.log({ ...current, ...patch });
function merge(base: Profile, changes: Partial<Profile>): Profile {
const clean = Object.fromEntries(
Object.entries(changes).filter(([, v]) => v !== undefined)
);
return { ...base, ...clean } as Profile;
}
console.log(merge(current, patch));
Discussion