Using Partial: Making All Properties Optional
Introduction
When building TypeScript applications, you frequently encounter objects where only some properties are present — configuration overrides, update payloads, form values, and more. TypeScript's Partial
In this deep tutorial you'll learn what Partial
By the end you will be able to:
- Use Partial
confidently in interfaces, function signatures, and library APIs. - Recognize when Partial
is insufficient and implement DeepPartial equivalents. - Combine Partial
with Required, Pick, Omit, and conditional types for robust patterns. - Integrate Partial types with runtime validation tools like Zod or Yup to keep runtime checks aligned with compile-time types.
This guide assumes you're comfortable with basic TypeScript types, mapped types, and generics. If you're looking to tighten up how you type configuration objects or API payloads, this is the tutorial for you.
Background & Context
Partial
However, Partial
Throughout this article you'll see practical code examples and pattern recommendations. We'll also link to related advanced typing guides such as how to type configuration objects and validate runtime structures so you can integrate Partial
Key Takeaways
- Partial
makes all properties optional but only shallowly. - For nested objects, implement or use a DeepPartial
pattern. - Combine Partial
with Pick, Omit, Required, and generics for expressive APIs. - Use runtime validation (e.g., Zod/Yup) to enforce Partial shapes at runtime.
- Prefer narrow, explicit types for public library surfaces and use Partial
in internal helpers. - Be cautious with unions and overloads — results can be unintuitive without careful design.
Prerequisites & Setup
To follow the examples you'll need:
- Node.js and npm/yarn (any recent stable version)
- TypeScript (>= 4.x recommended) installed locally or globally
- A code editor such as VS Code with TypeScript support
Optional but recommended for runtime validation examples:
- zod or yup installed in a demo project (we'll use zod in examples but show patterns that apply to yup too).
Install TypeScript and zod quickly:
npm install --save-dev typescript npm install zod
Open your tsconfig.json with at least "strict": true for the best type feedback.
Main Tutorial Sections
What Partial Does (Shallow Optionality)
Partial
type Partial<T> = {
[P in keyof T]?: T[P];
}That means for each property P on T, Partial
type User = { id: number; name: string; settings: { theme: string } };
type PartialUser = Partial<User>;
// PartialUser = { id?: number; name?: string; settings?: { theme: string } }Note that settings is optional as a whole, but if settings exists its shape still requires theme. This is the shallow behavior: nested properties are not made optional by the built-in Partial
Use Partial
Shallow vs Deep Partial: When Shallow is Not Enough
Shallow optionality fails when nested structures are updated partially. Consider a PATCH endpoint that accepts partial updates for a nested settings object:
// You want to accept { settings: { theme?: 'dark' } }
function updateUser(id: number, changes: Partial<User>) { /* ... */ }With Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};Careful: the naive approach treats functions and arrays as objects — you may want to exclude those cases. DeepPartial is a common pattern in libraries and internal utilities.
Using Partial with Functions and APIs
Partial
type UpdateUserPayload = Partial<User>;
function patchUser(id: number, payload: UpdateUserPayload) {
// apply shallow updates
}When creating public HTTP APIs, it's important to document whether nested updates are supported and validate payloads at runtime. For guidance on strict API payload typing and validation patterns, see our guide on Typing API Request and Response Payloads with Strictness.
Practical tip: prefer explicit DTOs for public endpoints (e.g., UpdateUserDTO) instead of exposing domain interfaces directly. That gives you a stable typed surface while allowing Partial-like flexibility internally.
Combining Partial with Required, Pick, and Omit
You can mix utility types to express more precise shapes. Examples:
// Make all optional, but require 'id' type UpdateUser = Partial<User> & Required<Pick<User, 'id'>>; // Omit metadata and make the rest partial type PartialNoMeta = Partial<Omit<User, 'metadata'>>;
This helps when some fields must be present for an operation (like id for an update) while others remain optional. When working with complex union types, be mindful of distributive mapped types and how union members interact with Partial; see our notes on union & intersection typing techniques for related patterns.
Partial with Generics and Library APIs
If you write generic functions or library APIs that accept partial shapes, type parameters let you keep inference helpful:
function merge<T>(defaults: T, overrides: Partial<T>): T {
return { ...defaults, ...overrides } as T;
}
const base = { a: 1, b: 2 };
merge(base, { b: 3 }); // inferred as { a: number; b: number }When your API involves more complex generic signatures, study advanced patterns for precise inference. Our guide on Typing Libraries With Complex Generic Signatures — Practical Patterns covers many strategies you can apply when Partial
Practical Example: Typing Configuration Objects
Configuration objects are classic use-cases for Partial
type AppConfig = { host: string; port: number; debug: boolean; db: { url: string; pool: number } };
function createApp(config: Partial<AppConfig> = {}) {
const defaults: AppConfig = { host: 'localhost', port: 3000, debug: false, db: { url: '', pool: 5 } };
const merged = deepMerge(defaults, config); // deepMerge must handle nested objects
return startApp(merged);
}For a complete guide on strict configuration typing and runtime validation patterns, consult our article on Typing Configuration Objects in TypeScript: Strictness and Validation.
Key implementation notes:
- Use a deep merge utility that performs predictable merges for nested objects.
- Validate merged output at startup; runtime validation prevents surprising runtime errors.
Partial with Runtime Validation (Zod/Yup integration)
Types are erased at runtime. To enforce Partial shapes coming from the network or user input, use a runtime schema library and allow partial validation. With zod you can mark sub-schemas as partial:
import { z } from 'zod';
const UserSchema = z.object({ id: z.number(), name: z.string(), settings: z.object({ theme: z.string() }) });
const PartialUserSchema = UserSchema.partial();
// parseInput may throw; handle errors accordingly
const payload = PartialUserSchema.parse({ name: 'sam' });This keeps runtime checks aligned to compile-time Partial
Common pitfall: PartialSchemas may accept undefined values; decide whether you expect undefined vs missing and write your schema accordingly.
Handling Nested Objects: Implementing DeepPartial Safely
A safer DeepPartial implementation should avoid turning functions or arrays into recursive partials unless intended. Here's a practical pattern:
type Builtin = string | number | boolean | Date | Function | RegExp;
type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
? Array<DeepPartial<U>>
: {
[K in keyof T]?: DeepPartial<T[K]>;
};This version keeps primitives and functions intact, makes arrays partial recursively, and maps objects deeply. Use this in deep-merge helpers and patch endpoints.
Testing, Type Guards, and Runtime Safety with Partial
When working with Partial
function hasName(obj: Partial<User>): obj is Partial<User> & { name: string } {
return typeof (obj as any).name === 'string';
}
if (hasName(payload)) {
console.log(payload.name.toUpperCase());
}Combine compile-time Partial
Interoperability with Advanced Type Patterns
Partial
type A = { a: number };
type B = { b: string };
type U = A | B;
type PU = Partial<U>; // Equivalent to Partial<A> | Partial<B>This distribution might not match your intent for a single object that can have a or b optionally. When building complex libraries, study how Partial interacts with other patterns; our article on Typing Libraries That Use Union and Intersection Types Extensively covers many of these interactions.
Also, when applying Partial to types that are produced via mixins or class-based patterns, ensure the shape you declare matches runtime behavior. See Typing Mixins with ES6 Classes in TypeScript — A Practical Guide for common pitfalls and typing recipes.
Advanced Techniques
Once you're comfortable with Partial
- Use branded or opaque types for fields that should never be partial in certain contexts (e.g., database IDs), combining Partial with Required picks.
- Create helper wrappers like mergeWithDefaults
(defaults: T, overrides?: Partial ) that encapsulate merging and validation logic — centralizing this reduces duplication. - For library surfaces, prefer explicit DTO types instead of exposing domain models directly as Partial. Use generics to keep inference ergonomic.
- Avoid over-using DeepPartial on wide types (e.g., huge nested configs). It can mask missing invariants; favor explicit optional fields to document intent.
- Bench test heavy deep-merge logic if it's on a hot path — deeply recursive merges can add CPU and memory overhead.
When designing these enhancements, consult more specialized guides for complex generics and overloaded APIs; these techniques are covered in Typing Libraries With Complex Generic Signatures — Practical Patterns and Typing Libraries With Overloaded Functions or Methods — Practical Guide.
Best Practices & Common Pitfalls
Dos:
- Use Partial
for shallow optional updates and builder patterns. - Prefer explicit DTOs for public APIs; use Partial internally.
- Combine Partial with Required<Pick<...>> to enforce crucial fields.
- Validate at runtime for user-supplied Partial inputs.
Don'ts:
- Don’t assume Partial
makes nested properties optional — use DeepPartial if you need that. - Avoid returning Partial
from public API surfaces (it can be vague and unhelpful for consumers). - Don’t ignore union distribution behavior; Partial can produce surprising union shapes.
Troubleshooting tips:
- If TypeScript inference becomes weak, add explicit generic parameters to help the compiler.
- Use utility type visualization in the TypeScript language server (hover) to inspect resulting types.
- When behavior differs between compile-time and runtime, add zod/yup schemas to ensure runtime contracts.
For configuration-specific patterns and validating startup configurations, check Typing Configuration Objects in TypeScript: Strictness and Validation.
Real-World Applications
Partial
- PATCH endpoints: accept shallow updates for top-level fields while requiring validation for nested fields.
- Component props: in UI libraries, Partial can be used for defaultable props internally while exposing clear prop types to consumers.
- Configuration & builders: allow consumers to pass just overrides and merge them with defaults at startup.
- Library convenience functions: merge, extend, or fill helpers often accept Partial
to ease ergonomics.
When you publish a library that accepts Partial
Conclusion & Next Steps
Partial
If you're building libraries or advanced helpers, read up on complex generics and overload patterns to ensure your Partial-based APIs remain ergonomic and type-safe. Recommended next reads: Typing Libraries With Complex Generic Signatures — Practical Patterns and Typing Libraries With Overloaded Functions or Methods — Practical Guide.
Enhanced FAQ
Q1: What exactly does Partial
Q2: When should I use DeepPartial instead of Partial? A2: Use DeepPartial when you need nested properties to be optional recursively (e.g., partial updates to nested configuration). Implement DeepPartial carefully to avoid unintentionally partializing functions or types you want preserved.
Q3: How do I validate Partial
Q4: Are there performance implications to using DeepPartial or deep-merge at runtime? A4: Yes — deep merges and recursive operations add CPU and memory overhead. For hot paths, benchmark and consider more efficient merge strategies or shallow merges with explicit logic. Also, extremely complex types can slow down TypeScript's compiler type-checking.
Q5: How does Partial
Q6: Should I return Partial
Q7: How do I require certain fields while making the rest optional?
A7: Combine Partial with Required and Pick: type UpdateX = Partial
Q8: How do I handle arrays in DeepPartial? A8: You can special-case arrays: map arrays to Array<DeepPartial> so each element is deep-partialized. Pay attention to tuples and readonly arrays and handle them separately if needed.
Q9: What about functions and methods inside objects — should they be partialized? A9: Usually no. Treat functions as builtins and don't recurse into them. In DeepPartial implementations exclude Function from recursion so methods keep their signatures intact.
Q10: Any tips when using Partial in combination with mixins or classes? A10: Yes — when using mixins or class-based patterns, ensure the runtime shape you construct matches the type you're declaring. Mixins can create properties dynamically; using Partial on classes may hide missing initialization. For advice on typing mixin patterns, consult Typing Mixins with ES6 Classes in TypeScript — A Practical Guide.
Q11: Can Partial
Q12: How do I document expected behavior for Partial
Q13: Where can I learn more about combining Partial with complex generics and overloads? A13: Dive into resources on advanced generic signatures and overload typing, such as Typing Libraries With Complex Generic Signatures — Practical Patterns and Typing Libraries With Overloaded Functions or Methods — Practical Guide. These go deeper into inference, conditional types, and ergonomics.
Q14: Are there helper libraries that provide DeepPartial-like types? A14: Some utility type libraries and frameworks include DeepPartial variations. Often it's better to implement a tailored DeepPartial to match your project's expectations (arrays, functions, maps). Keep the implementation small and well-tested.
Q15: How does Partial interact with mapped types like Readonly and Record?
A15: You can compose them: Readonly<Partial
If you want, I can generate ready-to-run example projects showing Partial, DeepPartial, merge helpers, and zod schemas — tell me which example you'd like first and I'll scaffold it for you.
