Partial & Required
Two of the most commonly used utility types in TypeScript are Partial<T> and Required<T>. They are opposites: Partial makes every property optional, while Required makes every property mandatory. Understanding both — and when to reach for each — will sharpen your API design immediately.
Partial<T>
Partial<T> takes a type T and produces a new type where every property is marked optional with ?. The original type is not modified.
interface User {
id: number;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
}
type PartialUser = Partial<User>;
/*
{
id?: number;
name?: string;
email?: string;
role?: "admin" | "editor" | "viewer";
}
*/How Partial Is Implemented
Under the hood, Partial<T> is a mapped type that iterates over every key of T and adds ?:
// TypeScript's built-in definition (from lib.es5.d.ts)
type Partial<T> = {
[P in keyof T]?: T[P];
};
// You could write the same thing manually:
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
// They are functionally identical
type A = Partial<User>;
type B = MyPartial<User>;
// A and B are the same typeWhen to Use Partial
The most common use cases for Partial<T>:
PATCH / update functions — the caller only supplies the fields they want to change.
Default / config objects — allow partial overrides that get merged with defaults.
Form state before validation — fields start empty and fill in as the user types.
Test fixtures — supply only the properties relevant to the test, let the rest default.
Builder patterns — accumulate properties one at a time before finalizing.
Update Function Pattern
interface User {
id: number;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
}
// In-memory store
const users = new Map<number, User>();
// Partial<User> so callers supply only the fields they want to change
function updateUser(id: number, patch: Partial<User>): User {
const existing = users.get(id);
if (!existing) throw new Error(`User ${id} not found`);
const updated: User = { ...existing, ...patch, id }; // id is always preserved
users.set(id, updated);
return updated;
}
// Valid — only updating the name
updateUser(1, { name: "Alice" });
// Valid — updating multiple fields at once
updateUser(1, { name: "Bob", role: "admin" });Config Objects with Defaults
interface ServerConfig {
host: string;
port: number;
maxConnections: number;
timeout: number;
debug: boolean;
}
const DEFAULT_CONFIG: ServerConfig = {
host: "localhost",
port: 3000,
maxConnections: 100,
timeout: 5000,
debug: false,
};
// Callers only need to provide the fields they want to override
function createServer(options: Partial<ServerConfig> = {}): ServerConfig {
return { ...DEFAULT_CONFIG, ...options };
}
const server = createServer({ port: 8080, debug: true });
// { host: "localhost", port: 8080, maxConnections: 100, timeout: 5000, debug: true }Partial argument.Required<T>
Required<T> is the mirror image of Partial<T>. It takes a type T and produces a new type where every property is required — all ? modifiers are removed.
interface UserForm {
name?: string;
email?: string;
password?: string;
}
type ValidatedUser = Required<UserForm>;
/*
{
name: string;
email: string;
password: string;
}
*/
// This now requires all three fields — no optionals
function createUser(data: ValidatedUser): void {
console.log(`Creating user: ${data.name} <${data.email}>`);
}How Required Is Implemented
Required<T> uses the -? mapped type modifier, which strips the optional marker from each property:
// TypeScript's built-in definition
type Required<T> = {
[P in keyof T]-?: T[P];
};
// The -? syntax removes the ? modifier
// Compare with +? which adds it (what Partial uses internally)
// You can also use -readonly to remove readonly modifiers:
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};When to Use Required
After validation — convert a partially filled form type into a fully populated entity.
Internal vs external APIs — accept optional fields externally, require them internally after defaulting.
Testing — assert that a function filled in all expected fields.
Ensuring all config options are resolved before use.
Form Validation Pattern
interface SignupForm {
name?: string;
email?: string;
password?: string;
acceptedTerms?: boolean;
}
type ValidSignup = Required<SignupForm>;
function validate(form: SignupForm): form is ValidSignup {
return (
typeof form.name === "string" &&
typeof form.email === "string" &&
typeof form.password === "string" &&
form.acceptedTerms === true
);
}
function handleSignup(raw: SignupForm): void {
if (!validate(raw)) {
console.error("Form is incomplete");
return;
}
// raw is now narrowed to ValidSignup — all fields present
console.log(`Welcome, ${raw.name}!`);
}Partial vs Required — Side by Side
Scenario | Use Partial | Use Required |
|---|---|---|
HTTP PATCH endpoint | Yes — only changed fields needed | No |
HTTP POST endpoint (create) | No | Yes — all fields needed |
Library config with defaults | Yes — callers override selectively | No |
Post-validation entity | No | Yes — all fields guaranteed |
Optional builder accumulation | Yes | No |
Final builder result | No | Yes |
Combining Partial and Required
You can combine them to make some fields required and others optional. Use intersection types or Pick/Omit to split the type first:
interface Article {
id: number;
title: string;
body: string;
tags?: string[];
publishedAt?: Date;
}
// id and title are always required in updates; everything else is optional
type ArticleUpdate = Required<Pick<Article, "id" | "title">> &
Partial<Omit<Article, "id" | "title">>;
function updateArticle(update: ArticleUpdate): void {
console.log(`Updating article ${update.id}: ${update.title}`);
}
// id and title must be provided:
updateArticle({ id: 1, title: "Hello" }); // ok
updateArticle({ id: 2, title: "World", tags: ["ts"] }); // ok
// updateArticle({ title: "No ID" }); // Error: id is missingPractical Pattern: Staged Builder
interface EmailMessage {
to: string;
subject: string;
body: string;
cc?: string;
bcc?: string;
}
class EmailBuilder {
private data: Partial<EmailMessage> = {};
to(address: string): this {
this.data.to = address;
return this;
}
subject(s: string): this {
this.data.subject = s;
return this;
}
body(b: string): this {
this.data.body = b;
return this;
}
cc(address: string): this {
this.data.cc = address;
return this;
}
build(): EmailMessage {
const { to, subject, body } = this.data;
if (!to || !subject || !body) {
throw new Error("to, subject, and body are required");
}
return this.data as EmailMessage; // safe after validation
}
}
const email = new EmailBuilder()
.to("alice@example.com")
.subject("Hello!")
.body("Nice to meet you.")
.build();
console.log(email.to); // "alice@example.com"alice@example.com
Partial and Required are both shallow — they only affect the top level of the type. If you need to affect nested properties too, write a DeepPartial or DeepRequired custom mapped type.Quick Summary
Utility | Modifier Used | Effect |
|---|---|---|
Partial<T> | +? on all keys | Every property becomes optional |
Required<T> | -? on all keys | Every property becomes required |
Readonly<T> | +readonly on all keys | Every property becomes read-only |
Mutable<T> (custom) | -readonly on all keys | Removes all readonly modifiers |
Partial when you accept a subset of fields (updates, configs, fixtures) and Required when you need to guarantee all fields are present (validated entities, finalized builders).