Utility Types Part 1: Partial and Required


Object types in TypeScript specify which properties exist, what types they hold, and whether each is optional or required. In most cases these details are set once in the type definition and stay fixed. But many operations on objects naturally require a different optionality profile than the base type: an update operation that only changes some properties, a configuration object where every field has a default, a form state that starts empty before the user fills it in.
Writing a separate type for each of these scenarios — manually prefixing every property with ? or removing ? from every property — is tedious and fragile. Change the base type and you must update every derived variant by hand. TypeScript's utility types solve this with a single generic: Partial<T> produces a version of T where every property is optional, and Required<T> produces a version where every property is required. Both are built into the TypeScript standard library and require no imports.
What Utility Types Are
Utility types are generic type aliases defined in TypeScript's lib.es5.d.ts declaration file. They accept one or more type parameters and produce a new type by applying a transformation. You use them the same way you use any generic type — by passing a type argument in angle brackets:
type User = {
id: number;
name: string;
email: string;
role: "admin" | "user";
};
type PartialUser = Partial<User>;
// Equivalent to:
// {
// id?: number;
// name?: string;
// email?: string;
// role?: "admin" | "user";
// }All utility types are pure type-level constructs. They exist only at compile time, produce no runtime code, and have zero performance cost at runtime. They are syntactic shorthand for mapped type patterns that you could write yourself — but because they are standardised and named, they communicate intent to anyone reading the code.
Utility Types Are Not Magic
Every utility type in TypeScript's standard library is implemented using
mapped types, conditional types, and the infer keyword — the same tools you
have access to. Looking at how they are defined (navigate to their definitions
in your editor with "Go to Definition") is one of the best ways to understand
advanced TypeScript type patterns. Partial, Required, Readonly, Pick,
Omit, and the rest are each a few lines of type-level code.
Partial
Partial<T> takes an object type T and returns a new type where every property of T is optional. Properties that were already optional remain optional. Properties that were required become optional.
type User = {
id: number;
name: string;
email: string;
};
type PartialUser = Partial<User>;
// {
// id?: number | undefined;
// name?: string | undefined;
// email?: string | undefined;
// }The resulting type PartialUser can be constructed with any subset of properties — including none at all:
const empty: PartialUser = {};
const nameOnly: PartialUser = { name: "Alice" };
const full: PartialUser = { id: 1, name: "Alice", email: "[email protected]" };All three are valid. Partial<T> does not validate that any properties are present — it only ensures that any properties that are present match their declared types.
How Partial Is Implemented
Partial<T> is defined in the TypeScript standard library as:
type Partial<T> = {
[P in keyof T]?: T[P];
};This is a mapped type — it iterates over every key P in keyof T (the union of all property keys of T) and produces a new property with the ? modifier added. The property type T[P] is the indexed access type for that key — the original value type, unchanged.
Understanding this implementation reveals what Partial does and does not do:
- It applies
?to every property, including nested object properties - It does not recursively apply to nested objects — a nested object property becomes optional, but the object itself is not made partial
- The value types of each property are preserved exactly as in
T
This last point is important: Partial<User> makes User.id optional, but the type of id when present is still number — not number | undefined. The ? modifier adds undefined to the assignable types but does not change the declared value type.
Required
Required<T> is the complement of Partial. It takes a type T and returns a version where every optional property has its ? modifier removed — making all properties required.
type Config = {
timeout?: number;
retries?: number;
baseUrl?: string;
debug?: boolean;
};
type RequiredConfig = Required<Config>;
// {
// timeout: number;
// retries: number;
// baseUrl: string;
// debug: boolean;
// }RequiredConfig requires all four properties. A value of type RequiredConfig cannot omit any of them:
// Error: Property 'retries' is missing
const cfg: RequiredConfig = {
timeout: 5000,
baseUrl: "https://api.example.com",
debug: false,
};Required is most useful when you have a configuration type with many optional fields (because they have defaults), but at some point in the application — after applying defaults — you need to work with a fully resolved configuration where every value is guaranteed to be present.
How Required Is Implemented
Required<T> is defined as:
type Required<T> = {
[P in keyof T]-?: T[P];
};The -? syntax is the mapped type modifier removal operator. +? adds the optional modifier; -? removes it. Required uses -? to strip the ? from every property key in the mapped type. Properties that were already required are unaffected.
Required Does Not Remove undefined from Value Types
Required<T> removes the optional modifier from properties, but it does not remove undefined from explicit union types. If your type has value: string | undefined (not value?: string), Required will not remove the undefined. The distinction matters: { value?: string } is equivalent to { value?: string | undefined } — the ? makes undefined implicit. But { value: string | undefined } explicitly includes undefined in the value type and is unaffected by Required. Only the ? modifier is stripped, not explicit | undefined in the declared type.
Practical Patterns for Partial
Update DTOs (Data Transfer Objects). When updating a resource via an API, clients typically send only the properties they want to change. Partial<T> is the natural type for the update payload:
type Post = {
id: number;
title: string;
content: string;
publishedAt: Date;
authorId: number;
};
type PostUpdate = Partial<Omit<Post, "id" | "authorId">>;
// {
// title?: string;
// content?: string;
// publishedAt?: Date;
// }
function updatePost(id: number, changes: PostUpdate): Promise<Post> {
// Apply only the provided changes to the stored post
return applyChanges(id, changes);
}Combining Partial with Omit (covered in the next post) is a common pattern for update operations — exclude the immutable fields (id, authorId) and make the rest optional.
Form state. A form starts with no values filled in and accumulates them as the user types. Partial<T> is the natural type for in-progress form state:
type RegistrationForm = {
username: string;
email: string;
password: string;
confirmPassword: string;
};
type FormState = Partial<RegistrationForm>;
const [formState, setFormState] = useState<FormState>({});
function updateField<K extends keyof FormState>(key: K, value: FormState[K]) {
setFormState((prev) => ({ ...prev, [key]: value }));
}Builder patterns. Builder pattern implementations often accumulate properties step by step. Partial<T> represents the in-progress state:
class QueryBuilder {
private config: Partial<QueryConfig> = {};
select(fields: string[]): this {
this.config.fields = fields;
return this;
}
where(condition: string): this {
this.config.condition = condition;
return this;
}
build(): QueryConfig {
if (!this.config.fields) throw new Error("fields required");
return this.config as QueryConfig;
}
}Use Partial for Optional Configuration Parameters
Functions that accept configuration objects benefit from Partial<T> combined with a defaults merge. Define the full config type with all fields required, accept Partial<Config> as the parameter, and merge with defaults at the start of the function. This pattern gives callers maximum flexibility while the internal implementation works with a fully resolved config: const config: Config = { ...defaultConfig, ...userConfig }.
Practical Patterns for Required
Post-validation states. After validating or deserialising data, you often want to assert that all fields are present. Required<T> communicates that a previously-partial value has been fully populated:
function validateConfig(raw: Partial<Config>): Required<Config> {
if (!raw.timeout) throw new Error("timeout required");
if (!raw.baseUrl) throw new Error("baseUrl required");
if (!raw.retries) throw new Error("retries required");
// After validation, we know all fields are present
return raw as Required<Config>;
}Ensuring complete object construction. When constructing objects through a builder or accumulation pattern, Required can be used at the end of the chain to verify completeness:
function buildReport(parts: Partial<Report>): Required<Report> {
const complete = applyDefaults(parts);
assertComplete(complete); // throws if any field is missing
return complete;
}Deep Partial and Its Limits
Partial<T> only makes top-level properties optional. Nested object properties become optional (because the entire nested object becomes optional), but the properties of those nested objects remain as declared in the original type.
type Settings = {
display: {
theme: string;
fontSize: number;
};
notifications: {
email: boolean;
push: boolean;
};
};
type PartialSettings = Partial<Settings>;
// {
// display?: {
// theme: string; // Still required!
// fontSize: number; // Still required!
// };
// notifications?: {
// email: boolean; // Still required!
// push: boolean; // Still required!
// };
// }display and notifications are optional, but if either is provided, it must be provided in full. To make every property at every nesting level optional, you need a recursive DeepPartial type:
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;DeepPartial is not a built-in utility type — you define it yourself (or use a library). Whether you need shallow or deep partiality depends on your use case. Update DTOs typically benefit from deep partiality; form state management often works fine with shallow.
What Is Next
Partial and Required are the first two of TypeScript's built-in utility types. The next post covers Readonly and Record — two more utility types from the standard library. Readonly produces an immutable version of an object type; Record constructs a dictionary type from a set of keys and a value type.
Key Takeaways
Partial<T>adds?to every property inT, making them all optional; it is implemented as{ [P in keyof T]?: T[P] }Required<T>removes?from every property inT, making them all required; it is implemented as{ [P in keyof T]-?: T[P] }Partialis useful for update DTOs, form state, and configuration with defaults;Requiredis useful for post-validation states and asserting complete construction- Both utility types are shallow — they do not recursively transform nested object types; for deep transformation, write a recursive
DeepPartialorDeepRequiredcustom utility type Requiredremoves the optional modifier but does not remove explicit| undefinedfrom value types — the two are distinct in TypeScript's type system