TypeScript Partial<T>: Making All Properties Optional

TypeScript Partial&lt;T&gt;: Making All Properties Optional

The Partial utility type makes all properties optional:

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

type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }

Every property becomes optional (?).

Basic Syntax

Partial<Type>

Takes one type parameter and returns a new type with all properties optional.

Common Use Case: Update Functions

Update functions accept partial data:

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

function updateUser(id: number, updates: Partial<User>) {
    // updates can have any subset of User properties
    return fetch(`/api/users/${id}`, {
        method: 'PATCH',
        body: JSON.stringify(updates)
    });
}

// All valid
updateUser(1, { name: 'Alice' });
updateUser(1, { email: 'alice@example.com' });
updateUser(1, { name: 'Alice', age: 30 });

Form State Management

Track partially completed forms:

interface RegistrationForm {
    username: string;
    email: string;
    password: string;
    confirmPassword: string;
}

function FormComponent() {
    const [formData, setFormData] = useState<Partial<RegistrationForm>>({});
    
    function handleChange(field: keyof RegistrationForm, value: string) {
        setFormData({ ...formData, [field]: value });
    }
    
    function isComplete(data: Partial<RegistrationForm>): data is RegistrationForm {
        return !!(data.username && data.email && data.password && data.confirmPassword);
    }
}

Configuration Objects

Merge user config with defaults:

interface Config {
    theme: 'light' | 'dark';
    language: string;
    notifications: boolean;
    autoSave: boolean;
}

const defaultConfig: Config = {
    theme: 'light',
    language: 'en',
    notifications: true,
    autoSave: true
};

function createConfig(userConfig: Partial<Config>): Config {
    return { ...defaultConfig, ...userConfig };
}

const config = createConfig({ theme: 'dark' });
// { theme: 'dark', language: 'en', notifications: true, autoSave: true }

How Partial Works Internally

TypeScript implements Partial using mapped types:

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

For each property P in T, create an optional property with the same type.

Nested Partial (Deep Partial)

Partial only affects top-level properties:

interface User {
    name: string;
    address: {
        street: string;
        city: string;
    };
}

type PartialUser = Partial<User>;
// { name?: string; address?: { street: string; city: string } }

address is optional, but if provided, street and city are still required.

For deep partial, define a recursive type:

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

type DeepPartialUser = DeepPartial<User>;
// { name?: string; address?: { street?: string; city?: string } }

Partial vs Optional Properties

Partial makes existing properties optional. It doesn't add properties:

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

type PartialUser = Partial<User>;
// Can only have 'id' and 'name', both optional

// NOT equivalent to
interface FlexibleUser {
    [key: string]: any;  // Can have any properties
}

Combining with Required

Make some properties required:

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

type UpdateUser = Partial<User> & Required<Pick<User, 'id'>>;
// { id: number; name?: string; email?: string; bio?: string }

id is required, others optional.

Validation with Partial

Type guards verify completeness:

interface Product {
    id: string;
    name: string;
    price: number;
}

function isCompleteProduct(data: Partial<Product>): data is Product {
    return (
        typeof data.id === 'string' &&
        typeof data.name === 'string' &&
        typeof data.price === 'number'
    );
}

function saveProduct(data: Partial<Product>) {
    if (isCompleteProduct(data)) {
        // data is now typed as Product
        database.save(data);
    } else {
        throw new Error('Incomplete product data');
    }
}

API Request Types

interface Article {
    id: string;
    title: string;
    content: string;
    authorId: string;
    publishedAt: Date;
}

// POST /articles - omit server-generated fields
type CreateArticleRequest = Omit<Article, 'id' | 'publishedAt'>;

// PATCH /articles/:id - partial update
type UpdateArticleRequest = Partial<Omit<Article, 'id'>>;

class ArticleAPI {
    create(data: CreateArticleRequest) { }
    update(id: string, data: UpdateArticleRequest) { }
}

Class Constructors

Accept partial data with defaults:

class User {
    id: number;
    name: string;
    email: string;
    active: boolean;
    
    constructor(data: Partial<User> & Required<Pick<User, 'id'>>) {
        this.id = data.id;
        this.name = data.name ?? '';
        this.email = data.email ?? '';
        this.active = data.active ?? true;
    }
}

const user = new User({ id: 1, name: 'Alice' });

State Reducers

Redux/reducer patterns with partial updates:

interface State {
    user: User | null;
    loading: boolean;
    error: string | null;
}

type Action =
    | { type: 'UPDATE_USER'; payload: Partial<User> }
    | { type: 'SET_LOADING'; payload: boolean }
    | { type: 'SET_ERROR'; payload: string };

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case 'UPDATE_USER':
            return {
                ...state,
                user: state.user ? { ...state.user, ...action.payload } : null
            };
        case 'SET_LOADING':
            return { ...state, loading: action.payload };
        case 'SET_ERROR':
            return { ...state, error: action.payload };
    }
}

When Not to Use Partial

Don't use Partial when all fields are genuinely required:

// Bad - fields are required but typed as optional
function createUser(data: Partial<User>) {
    // Runtime errors if fields missing
    return database.insert(data);
}

// Good - explicit about requirements
function createUser(data: User) {
    return database.insert(data);
}

Partial with Generics

function merge<T>(target: T, source: Partial<T>): T {
    return { ...target, ...source };
}

const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
const updated = merge(user, { email: 'newemail@example.com' });

React Props Patterns

interface ComponentProps {
    title: string;
    onSave: () => void;
    onCancel: () => void;
}

// Default props pattern
const defaultProps: Partial<ComponentProps> = {
    onCancel: () => {}
};

function Component(props: ComponentProps) {
    const finalProps = { ...defaultProps, ...props };
    // ...
}

Further Reading

The TypeScript handbook's Partial documentation covers usage examples.

For advanced patterns, see mapped types documentation.

Matt Pocock's Total TypeScript explores utility type patterns.

Partial is one of TypeScript's most useful utility types for flexible function parameters and partial updates.

Wear the code

Product mockup

Partial<T> Developer T-Shirt (TypeScript Edition — Dark Mode)

£25.00

View product
Product mockup

Partial<T> Developer T-Shirt (TypeScript Edition — Light Mode)

£25.00

View product

0 comments

Leave a comment

Please note, comments need to be approved before they are published.