TypeScript Partial and Required

When you build applications with TypeScript, you constantly need slightly different shapes of the same object: a full User record from the database, but only a few fields when updating one; a Config object where every field is optional when the caller creates it, but every field must be present once the app actually uses it. Rather than writing a second interface by hand for each of these variations, TypeScript ships two built-in utility typesPartial<T> and Required<T> — that transform an existing type into a new one with all properties made optional or all properties made mandatory, respectively.

Overview / How it works

Partial<T> and Required<T> are mapped types: generic types that iterate over the keys of another type and produce a new type by applying a transformation to each property. TypeScript defines them internally (roughly) like this:

type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };

Partial<T> walks every key P in T and adds a ? modifier, making that property optional. Required<T> does the opposite: it applies -?, which explicitly removes an optional modifier if one is present, forcing every property to be defined. Both types are shallow — they only affect the top-level properties of T. If a property’s value is itself an object type, that nested object’s own properties are left untouched.

Because these are purely compile-time constructs, none of this exists once your code is compiled to JavaScript. TypeScript’s type system is erased at runtimePartial<T> and Required<T> never appear in the emitted .js file. They exist only so the compiler can catch mistakes (a missing required field, an unexpected extra field) before your code ever runs. This is also why you cannot check at runtime whether an object \”is\” a Partial<User> — at runtime it’s just a plain object, and it’s on you to make sure the shape actually matches what the type promised.

Both utility types rely on structural typing: TypeScript doesn’t care what an object is named or which interface it was declared against — it only checks whether the object’s shape is compatible with the target type. So a plain object literal with the right optional or required keys will satisfy Partial<T> or Required<T> without needing any explicit relationship to T.

Syntax

Partial<T>
Required<T>
  • T — any object type: an interface, a type alias for an object shape, or a class instance type.
  • Partial<T> — produces a new type identical to T, except every property becomes optional (prop?: Type), so it may be omitted entirely.
  • Required<T> — produces a new type identical to T, except every property becomes mandatory, even ones that were declared optional (prop?: Type) on T.

Neither type takes a second argument — if you want to make only some properties optional or required, you combine them with Pick<T, Keys> and intersection types, which is covered in other lessons in this section.

Examples

Example 1: Partial for update functions

The most common use of Partial<T> is an \”update\” or \”patch\” function, where the caller only supplies the fields they want to change.

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

function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates };
}

const user: User = { id: 1, name: \"Ada\", email: \"ada@example.com\", age: 30 };
const updated = updateUser(user, { age: 31 });
console.log(updated);

Output:

{ id: 1, name: 'Ada', email: 'ada@example.com', age: 31 }

Because updates is typed as Partial<User>, the caller can pass { age: 31 } without also supplying id, name, and email. If updates were typed as plain User, TypeScript would reject the call for missing properties.

Example 2: Required for finalized configuration

Required<T> is useful when a type has optional fields during construction, but you want to guarantee every field is filled in before the object is actually used.

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

function finalizeConfig(config: Required<Config>): void {
  console.log(`Connecting to ${config.host}:${config.port} (debug=${config.debug})`);
}

const cfg: Required<Config> = { host: \"localhost\", port: 8080, debug: false };
finalizeConfig(cfg);

Output:

Connecting to localhost:8080 (debug=false)

Config allows every field to be missing while the user is still building it up, but finalizeConfig demands a Required<Config>, so TypeScript forces every field to be present before the function will accept the argument. If you tried to pass an object missing port, the compiler would report an error at the call site.

Example 3: Combining Partial and Required for defaults + overrides

A very common real-world pattern is storing a fully-populated set of defaults typed with Required<T>, then merging in a Partial<T> of user overrides.

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

const defaultSettings: Required<Settings> = {
  theme: \"light\",
  fontSize: 14,
  notifications: true,
};

function createSettings(overrides: Partial<Settings> = {}): Settings {
  return { ...defaultSettings, ...overrides };
}

const userSettings = createSettings({ theme: \"dark\" });
console.log(userSettings);

Output:

{ theme: 'dark', fontSize: 14, notifications: true }

defaultSettings guarantees (via Required<Settings>) that every field has a value, so spreading it first always produces a complete object. overrides, typed as Partial<Settings>, lets the caller change only the fields they care about — here, just theme. Spreading overrides second means any field it does supply wins over the default.

Under the hood

Walk through what the compiler actually does when it sees Partial<User>:

  • It resolves keyof User to the union of property names: \"id\" | \"name\" | \"email\" | \"age\".
  • It maps over each key, taking that key’s original type from User and adding a ? modifier.
  • The result is a brand-new anonymous object type — structurally identical to User but with every property optional. It is not the same type as User, and TypeScript will not let you assign a Partial<User> back to a variable of type User without ensuring all fields are actually present.

Required<T> works the same way but with the -? modifier, which strips any existing ?. This mapped-type machinery is checked entirely during compilation. Once tsc emits JavaScript, Partial and Required vanish completely — the emitted code is just object literals and spreads, with no trace of which properties were \”optional\” in the type system. That’s why a Partial<User> object at runtime can genuinely be missing properties: there is no runtime check enforcing the shape, only the compiler’s static analysis before the code ever runs.

Common Mistakes

Mistake 1: Assuming Partial is deep

Because Partial<T> only affects top-level keys, nested object properties are still required in full when you do provide them.

interface Address {
  street: string;
  city: string;
}
interface Profile {
  name: string;
  address: Address;
}

const bad: Partial<Profile> = { address: { city: \"Shelbyville\" } };
// Error: Property 'street' is missing in type '{ city: string; }'
// but required in type 'Address'.

Here, address is optional on Partial<Profile> — you can leave it out entirely — but the moment you do supply an address, it must satisfy the full, unmodified Address shape, since Partial never touched Address‘s own properties. The fix is to supply the complete nested object, or to define a separate deep-partial type (not built in) if you truly need nested optionality:

interface Address {
  street: string;
  city: string;
}
interface Profile {
  name: string;
  address: Address;
}

const fixed: Partial<Profile> = { address: { street: \"1 Main St\", city: \"Shelbyville\" } };
console.log(fixed);

Output:

{ address: { street: '1 Main St', city: 'Shelbyville' } }

Mistake 2: Passing a Partial straight into a function expecting the full type

It’s easy to build up a draft object as Partial<T> and then forget it isn’t actually a T yet.

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

function saveUser(user: User): void {
  console.log(`Saving ${user.name} <${user.email}>`);
}

const draft: Partial<User> = { name: \"Grace\" };
saveUser(draft);
// Error: Argument of type 'Partial<User>' is not assignable to
// parameter of type 'User'. Property 'id' is missing in type
// '{ name: string; }' but required in type 'User'.

tsc correctly refuses this call, because draft might be missing id and email at runtime — and it is. The fix is to merge the draft with defaults (or otherwise guarantee completeness) before it reaches a function that requires the full type:

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

function saveUser(user: User): void {
  console.log(`Saving ${user.name} <${user.email}>`);
}

const defaults: User = { id: 0, name: \"\", email: \"\" };
const draft: Partial<User> = { name: \"Grace\" };
saveUser({ ...defaults, ...draft });

Output:

Saving Grace <>

Best Practices

  • Use Partial<T> for function parameters that represent \”patch\” or \”update\” data, not for your primary data model — your core interfaces should describe what a valid, complete object looks like.
  • Remember Partial<T> and Required<T> are shallow; for deeply nested optional structures, either restructure the type or write a custom deep-mapped type.
  • Prefer Required<T> at API boundaries where you want to guarantee a caller has filled in every configuration option, catching missing fields at compile time instead of as a undefined bug at runtime.
  • Don’t use Partial<T> as a way to avoid writing real optional-field types — if a field is genuinely always optional in your domain, mark it field?: Type directly on the interface instead of wrapping every usage in Partial.
  • Combine Partial<T> and Required<T> with Pick<T, K> or Omit<T, K> when you only want to change the optionality of a subset of properties, since neither utility supports a \”keys\” argument on its own.
  • Remember both types disappear at compile time — they give you no runtime guarantee, so still validate data coming from outside your program (API responses, JSON.parse, form input) rather than trusting a type annotation alone.

Practice Exercises

  • Exercise 1: Define an interface Product with id: number, name: string, price: number, and inStock: boolean. Write a function applyDiscount(product: Product, changes: Partial<Product>): Product that merges changes into product and returns the result.
  • Exercise 2: Define an interface FormState where every field (username, password, email) is optional. Write a function submitForm(form: Required<FormState>): void that logs all three fields, and call it with an object literal that supplies every field so it type-checks.
  • Exercise 3: Given interface Task { id: number; title: string; done: boolean; }, write a toggleDone function that accepts a Task and a Partial<Task> of overrides, and predict (then verify by reasoning through the type) what happens if you try to pass { id: undefined } as an override — does it type-check, and why?

Summary

  • Partial<T> makes every property of T optional; Required<T> makes every property of T mandatory, even ones declared with ?.
  • Both are mapped types built into TypeScript’s standard library, implemented with [P in keyof T] plus the ? or -? modifier.
  • Both transformations are shallow — they only affect the top-level keys of T, not nested object properties.
  • Like all TypeScript types, they are erased at compile time and have zero effect on the emitted JavaScript or on runtime behavior.
  • Typical uses: Partial<T> for patch/update inputs and default-override merging; Required<T> for enforcing that optional-during-construction fields are complete before final use.