Introduction: Why is this so commonly searched?
\n\nEvery developer who transitions from JavaScript to TypeScript or simply writes in this language sooner or later faces a problem: how to correctly set a default value for an object passed to a function? On Stack Overflow, this question has garnered thousands of views and dozens of answers. Why? Because a seemingly simple task — "if an object is not passed, use an empty object" — turns into a whole quest with types, interfaces, and destructuring in TypeScript.
\n\nIn this article, we will cover all possible methods, from the simplest to the most advanced, with code examples and explanations. You will learn how to set default values for a TypeScript object passed as an argument and avoid common mistakes. Let's go!
\n\nThe Problem: What's wrong with the usual approach?
\n\nLet's say we have a function that accepts a settings object:
\n\nfunction createUser(config: { name?: string; age?: number }) {\n // ...\n}\n\ncreateUser(); // Error! Argument required\n\n\nIn JavaScript, we could simply write function createUser(config = {}) and be done with it. But in TypeScript, this trick won't work: the compiler will complain about a type mismatch. Why? Because the empty object {} does not match the interface { name?: string; age?: number } — it simply doesn't have these properties.
And here's where the interesting part begins. Let's break down all the solutions.
\n\nMethod 1: Destructuring with default value assignment
\n\nThe most elegant and popular method is to use destructuring directly in the function parameters. You extract the object's properties and immediately assign default values to them:
\n\nfunction createUser({ name = "Guest", age = 18 }: { name?: string; age?: number } = {}) {\n console.log(`User created: ${name}, age: ${age}`);\n}\n\ncreateUser(); // "User created: Guest, age: 18"\ncreateUser({ name: "Anna" }); // "User created: Anna, age: 18"\ncreateUser({ age: 25 }); // "User created: Guest, age: 25"\n\n\nNotice the = {} at the end. This is the default value for the object itself. If you don't pass an argument, TypeScript uses an empty object, and destructuring substitutes the default values for each property.
Pros: clean, concise, works like a charm.
\nCons: you need to explicitly specify the type for each property.
Method 2: Using an interface with Partial
\n\nIf your object is large, describing all types in the parameters is bad practice. Extract the interface separately and use Partial<T>:
interface UserConfig {\n name: string;\n age: number;\n email: string;\n isAdmin: boolean;\n}\n\nfunction createUser(config: Partial<UserConfig> = {}) {\n const defaults: UserConfig = {\n name: "Guest",\n age: 18,\n email: "noemail@example.com",\n isAdmin: false\n };\n \n const mergedConfig = { ...defaults, ...config };\n console.log(mergedConfig);\n}\n\ncreateUser({ name: "Ivan" });\n// { name: "Ivan", age: 18, email: "noemail@example.com", isAdmin: false }\n\n\nHere, Partial<UserConfig> makes all properties optional. Inside the function, we merge the default values with the passed ones. It works great but requires manual merging.
Method 3: Classic approach with checking
\n\nSometimes you need more control. For example, if you want to set a default value only for undefined, but not for null. Use the good old if:
function createUser(config?: UserConfig) {\n const name = config?.name ?? "Guest";\n const age = config?.age ?? 18;\n const email = config?.email ?? "noemail@example.com";\n \n console.log({ name, age, email });\n}\n\ncreateUser();