You build a form, define a nice TypeScript type for your form data, submit it… and suddenly half your fields are undefined. You try to patch values inside the function, but your code starts to feel messy and you lose confidence in your type safety.
A cleaner way is to design your types and helper functions so that default values are applied in one place and your app always works with a fully initialized object. Think of a simple user profile form: if the user skips a field like “newsletter”, you still want it to behave as false instead of undefined.
In this article, I’ll show you 4 ways to do set default values for TypeScript types in TypeScript.
Our running example: User Profile type
To keep things simple and practical, we’ll use the same example in every method: a user profile form.
We’ll start with a base TypeScript type called UserProfile:
type UserProfile = {
name: string;
email: string;
age?: number;
country?: string;
newsletter?: boolean;
};Here:
- name and email are required strings.
- age, country, and newsletter are optional, so they can be missing or
undefined.
The goal in all methods: turn a partial UserProfile (raw form data or API data) into a fully initialized UserProfile where every field has a sensible default value.
Method 1 – Use a default object and spread it
This method is the most straightforward and works in almost every TypeScript project. You keep a default object and merge user input into it.
Step 1: Define a default object for your type
type UserProfile = {
name: string;
email: string;
age?: number;
country?: string;
newsletter?: boolean;
};
const defaultUserProfile: UserProfile = {
name: "Anonymous",
email: "unknown@example.com",
age: 0,
country: "Unknown",
newsletter: false,
};How does this code work?
The defaultUserProfile object assigns a concrete value to every property in UserProfile. TypeScript checks that all required fields are present and that each value matches the declared type, so you get type safety right at the definition.
Pro Tip
Keep your defaultUserProfile in a single file and reuse it. This avoids magic values scattered across many functions.
Step 2: Merge user input with the default object
Imagine you get partial data from a form:
const partialInput: Partial<UserProfile> = {
name: "Alice",
email: "alice@example.com",
country: "India",
};Now you merge:
function createUserProfile(input: Partial<UserProfile>): UserProfile {
return {
...defaultUserProfile,
...input,
};
}
const finalProfile = createUserProfile(partialInput);
console.log(finalProfile);Expected output:
{
name: "Alice",
email: "alice@example.com",
age: 0,
country: "India",
newsletter: false
}I executed the above example code and added the screenshot below.

How does this code work?
The spread operator ... copies all properties from defaultUserProfile into a new object, then copies properties from input on top, overwriting matching keys. Because input is Partial<UserProfile>, TypeScript allows missing fields but still enforces correct property names and types.
Pro Tip
Always type your input as Partial<UserProfile> when you expect missing fields. This tells TypeScript exactly which keys are allowed while still letting you merge defaults safely.
Method 2 – Use a helper function with parameter defaults
If you often build a fully initialized object from raw values, you can wrap the logic in a factory function that uses default parameters.
Step 1: Create a typed factory function
type UserProfile = {
name: string;
email: string;
age: number;
country: string;
newsletter: boolean;
};
function makeUserProfile(
name: string = "Anonymous",
email: string = "unknown@example.com",
age: number = 0,
country: string = "Unknown",
newsletter: boolean = false
): UserProfile {
return {
name,
email,
age,
country,
newsletter,
};
}How does this code work?
Each parameter in makeUserProfile has a default value. When you call the function without a specific argument, TypeScript and JavaScript use the default value instead. The return type UserProfile guarantees that the function always returns a fully initialized profile object with no undefined fields.
Pro Tip
Use default parameters when you often create a fully initialized instance from scratch, not when you are merging big objects. It keeps the function signature easy to read and avoids nested spreading.
Step 2: Use the factory function with and without arguments
const userWithDefaults = makeUserProfile();
const userWithNameOnly = makeUserProfile("Bob");
const userFull = makeUserProfile(
"Charlie",
"charlie@example.com",
25,
"India",
true
);
console.log(userWithDefaults);
console.log(userWithNameOnly);
console.log(userFull);
Expected output (simplified):
{
name: "Anonymous",
email: "unknown@example.com",
age: 0,
country: "Unknown",
newsletter: false
}
{
name: "Bob",
email: "unknown@example.com",
age: 0,
country: "Unknown",
newsletter: false
}
{
name: "Charlie",
email: "charlie@example.com",
age: 25,
country: "India",
newsletter: true
}I executed the above example code and added the screenshot below.

How does this code work?
When you call makeUserProfile with fewer arguments, the missing ones fall back to their default values. TypeScript still checks the types of any provided arguments and ensures the return type matches UserProfile, so your calling code always gets a complete and safe object.
Pro Tip
Avoid long parameter lists. If you have many fields, switch to an options object and combine this method with Method 1 so you can pass a singlePartial<UserProfile>argument.
Method 3 – Use a typed applyDefaults utility
In a real project, you’ll likely need default handling for many TypeScript types, not just UserProfile. A small TypeScript generic helper function makes this reusable.
Step 1: Create a generic utility for default values
type UserProfile = {
name: string;
email: string;
age?: number;
country?: string;
newsletter?: boolean;
};
const defaultUserProfile: UserProfile = {
name: "Anonymous",
email: "unknown@example.com",
age: 0,
country: "Unknown",
newsletter: false,
};
function applyDefaults<T>(
defaults: T,
input: Partial<T>
): T {
return {
...defaults,
...input,
};
}How does this code work?
The applyDefaults function is generic: the type parameter T represents any TypeScript object type. The defaults argument must be a complete T, whereas input is Partial<T>, allowing missing keys. Inside the function, spreading defaults and input is type-safe, and the function returns an object typed as T, so the caller gets a fully initialized value.
Pro Tip
This pattern works great for configuration objects, API options, and UI settings. Keep applyDefaults in a shared utilities file and use it whenever you combine user config with library defaults.
Step 2: Use the utility with your profile type
const rawFormData: Partial<UserProfile> = {
name: "Disha",
email: "disha@example.com",
};
const normalizedProfile = applyDefaults<UserProfile>(
defaultUserProfile,
rawFormData
);
console.log(normalizedProfile);Expected output:
{
name: "Disha",
email: "disha@example.com",
age: 0,
country: "Unknown",
newsletter: false
}I executed the above example code and added the screenshot below.

How does this code work?
When you call applyDefaults<UserProfile>, you explicitly set the generic type T to UserProfile. TypeScript verifies that defaultUserProfile conforms to UserProfile and that rawFormData only uses valid keys from UserProfile. The returned normalizedProfile is a complete UserProfile, and your editor can show the type when you hover over it, which makes debugging easier.
Pro Tip
Let TypeScript infer the generic type when possible: applyDefaults(defaultUserProfile, rawFormData) often works without <UserProfile>. Only annotate when inference gets confused or when you want extra clarity.
Method 4 – Use interfaces with optional properties and default instances
Sometimes you use a TypeScript interface instead of a type alias and still need clear default values. The idea is the same: keep a default instance and use it everywhere.
Step 1: Define an interface and its default instance
interface UserProfile {
name: string;
email: string;
age?: number;
country?: string;
newsletter?: boolean;
}
const defaultUserProfileInterface: UserProfile = {
name: "Anonymous",
email: "unknown@example.com",
age: 0,
country: "Unknown",
newsletter: false,
};How does this code work?
The UserProfile interface describes the shape of the object, while defaultUserProfileInterface is a concrete value that implements that shape. TypeScript checks that all required interface members are present and that each property has the correct type. You now have a single, trusted source of defaults for any code that uses the interface.
Pro Tip
Whether you use interface or type, the default pattern is the same. Choose interface when you want declaration merging or TypeScript class implementation, and type when you need unions or mapped types.
Step 2: Normalize partial objects using the interface default
function normalizeUserProfile(
input: Partial<UserProfile>
): UserProfile {
return {
...defaultUserProfileInterface,
...input,
};
}
const apiUser: Partial<UserProfile> = {
name: "Esha",
email: "esha@example.com",
newsletter: true,
};
const safeUser = normalizeUserProfile(apiUser);
console.log(safeUser);
Expected output:
{
name: "Esha",
email: "esha@example.com",
age: 0,
country: "Unknown",
newsletter: true
}How does this code work?
The normalizeUserProfile function takes a Partial<UserProfile so callers can pass incomplete data, but it always returns a full UserProfile by merging defaultUserProfileInterface with the input. This keeps the rest of your app simple, because downstream code can ignore undefined checks and treat all fields as initialized.
Pro Tip
Use this pattern right at your data boundaries: when you receive data from an API or a database, normalize it into a fully initialized UserProfile once, and avoid sprinklingx ?? defaultchecks across your UI code. [LINK: Handling API Responses Safely in TypeScript]
Things to Keep in Mind
- undefined is the real default
Any declared variable in TypeScript and JavaScript starts asundefinedif you don’t assign a value, so never rely on a “hidden” default like0or empty string. - Use Partial<T> for incoming data
When working with forms, APIs, or configuration, use Partial<Type> to model “may be missing” fields and then apply defaults in one place. This keeps your types honest and your defaults obvious. - Avoid
anyfor default handling
If you declare defaults using any, you lose type safety and autocompletion. Prefer the real type (UserProfile, interface, or generic T) so the compiler catches wrong keys and wrong value types. - Watch strict null checks
With strict mode and strictNullChecks enabled,nullandundefinedbehave differently. Decide whether you wantundefinedornullas the “missing” state and reflect that clearly in your TypeScript type. - Be careful with union types
If you use a union type for defaults (for examplestring | "Unknown"), make sure your code always normalizes to one clear shape before you use the value in many places. Unions are powerful but can complicate default logic. - Centralize default logic
Don’t sprinkle default assignment across components and services. Keep a small set of helper functions or default objects and reuse them, so you can change defaults in one place without risky refactors.
You learned four practical ways to set default values for TypeScript types using default objects, factory functions, generic helpers, and interfaces. Use spread-based defaults for object merging, default parameters for simple constructors, and generic utilities when you want one pattern to work across many types. I hope you found this article helpful.
You may also like to read:
- Require vs Import in TypeScript
- Unknown vs Any in TypeScript
- TypeScript Objects With Advanced Type Safety
- TypeScript Switch Statements

Bijay Kumar is an experienced Python and AI professional who enjoys helping developers learn modern technologies through practical tutorials and examples. His expertise includes Python development, Machine Learning, Artificial Intelligence, automation, and data analysis using libraries like Pandas, NumPy, TensorFlow, Matplotlib, SciPy, and Scikit-Learn. At PythonGuides.com, he shares in-depth guides designed for both beginners and experienced developers. More about us.