Default Parameters: Giving Function Parameters Default Values
When you order coffee at a coffee shop, the barista might ask: "Would you like sugar?" If you don't answer, some coffee shops will default to no sugar, others will add sugar by default. This "default behavior" makes the ordering process smoother - you don't have to specify every detail each time. In JavaScript, default parameters provide similar convenience, allowing functions to automatically use preset default values when certain parameters are not provided.
What Are Default Parameters
Default Parameters are a feature introduced in ES6 that allows us to specify default values for parameters when declaring functions. If the function is called without providing that parameter, or if the provided value is undefined, the function will use the default value:
function greet(name = "Guest") {
console.log(`Hello, ${name}!`);
}
greet("Alice"); // Hello, Alice!
greet(); // Hello, Guest!In this simple example, if greet() is called without passing a name parameter, it uses the default value "Guest". This is more concise and clear than traditional parameter checking methods.
The Pre-ES6 Approach
Before default parameter syntax was available, we needed to manually check parameters and set default values:
// Common approach in ES5 and earlier
function greetOldWay(name) {
// Method 1: Using || operator
name = name || "Guest";
console.log("Hello, " + name + "!");
}
// Or
function greetMoreExplicit(name) {
// Method 2: Explicit undefined check
if (name === undefined) {
name = "Guest";
}
console.log("Hello, " + name + "!");
}
greetOldWay(); // Hello, Guest!
greetOldWay("Alice"); // Hello, Alice!While this approach works, it has some issues. When using the || operator, if the passed value is falsy (like 0, "", false), it will also be replaced with the default value:
function setCount(count) {
count = count || 10; // Problem: 0 is also treated as falsy
console.log(`Count: ${count}`);
}
setCount(5); // Count: 5
setCount(0); // Count: 10 (wanted to set to 0, but replaced with default value)
setCount(); // Count: 10ES6 default parameters only take effect when the parameter is undefined, avoiding this problem:
function setCount(count = 10) {
console.log(`Count: ${count}`);
}
setCount(5); // Count: 5
setCount(0); // Count: 0 (correctly handled 0)
setCount(); // Count: 10
setCount(undefined); // Count: 10How Default Parameters Work
Default parameters only take effect in two situations:
- Parameter not passed
- Explicitly passed
undefined
Passing other falsy values (such as null, 0, false, "") will not trigger the default value:
function test(value = "default") {
console.log(value);
}
test(); // "default" - no parameter passed
test(undefined); // "default" - explicitly passed undefined
test(null); // null - null does not trigger default value
test(0); // 0
test(false); // false
test(""); // "" - empty string does not trigger default valueThis is an important distinction. undefined means "no value", while null means "has a value, but the value is null". The design of default parameters follows this semantics:
function createUser(name, age = 18) {
return {
name: name,
age: age,
};
}
console.log(createUser("Alice"));
// { name: "Alice", age: 18 }
console.log(createUser("Bob", undefined));
// { name: "Bob", age: 18 }
console.log(createUser("Charlie", null));
// { name: "Charlie", age: null } - null does not trigger default valueMultiple Default Parameters
A function can have multiple default parameters, which can be used in any combination:
function bookFlight(destination, date = "2025-12-31", class = "economy", meal = "standard") {
console.log(`Booking flight to ${destination}`);
console.log(`Date: ${date}`);
console.log(`Class: ${class}`);
console.log(`Meal: ${meal}`);
}
bookFlight("Tokyo");
// Booking flight to Tokyo
// Date: 2025-12-31
// Class: economy
// Meal: standard
bookFlight("Paris", "2025-06-15");
// Booking flight to Paris
// Date: 2025-06-15
// Class: economy
// Meal: standard
bookFlight("London", "2025-08-01", "business");
// Booking flight to London
// Date: 2025-08-01
// Class: business
// Meal: standardSkipping Middle Parameters
If you want to use the default value of a certain parameter but set parameters after it, you can explicitly pass undefined:
function configure(host, port = 8080, protocol = "http", timeout = 5000) {
console.log(`${protocol}://${host}:${port} (timeout: ${timeout}ms)`);
}
// Want to use port's default value but customize protocol
configure("localhost", undefined, "https");
// https://localhost:8080 (timeout: 5000ms)
// Want to use port and protocol's default values but customize timeout
configure("localhost", undefined, undefined, 10000);
// http://localhost:8080 (timeout: 10000ms)While this works, multiple undefineds can affect readability. In such cases, using object parameters might be better:
function configure({ host, port = 8080, protocol = "http", timeout = 5000 }) {
console.log(`${protocol}://${host}:${port} (timeout: ${timeout}ms)`);
}
configure({
host: "localhost",
protocol: "https",
});
// https://localhost:8080 (timeout: 5000ms)
configure({
host: "localhost",
timeout: 10000,
});
// http://localhost:8080 (timeout: 10000ms)Default Parameter Expressions
The value of default parameters can be not just simple literals, but any valid JavaScript expression:
1. Function Call as Default Value
function getDefaultDate() {
return new Date().toISOString().split("T")[0]; // Returns today's date
}
function createEvent(name, date = getDefaultDate()) {
console.log(`Event: ${name} on ${date}`);
}
createEvent("Conference");
// Event: Conference on 2025-12-04
createEvent("Workshop", "2025-12-15");
// Event: Workshop on 2025-12-15Important: Default parameter expressions are calculated at call time, not definition time:
let counter = 0;
function increment(value = ++counter) {
console.log(value);
}
increment(); // 1 (counter becomes 1)
increment(); // 2 (counter becomes 2)
increment(10); // 10 (doesn't use default value, counter stays at 2)
increment(); // 3 (counter becomes 3)2. Referencing Other Parameters
Default parameters can reference preceding parameters:
function createUser(
firstName,
lastName,
fullName = `${firstName} ${lastName}`
) {
return {
firstName: firstName,
lastName: lastName,
fullName: fullName,
};
}
console.log(createUser("John", "Doe"));
// { firstName: "John", lastName: "Doe", fullName: "John Doe" }
console.log(createUser("Jane", "Smith", "Dr. Jane Smith"));
// { firstName: "Jane", lastName: "Smith", fullName: "Dr. Jane Smith" }However, you cannot reference subsequent parameters because they haven't been initialized yet:
// ❌ Error: cannot reference later parameters
// function invalid(a = b, b = 2) {} // ReferenceError: Cannot access 'b' before initialization
// ✅ Correct: can reference preceding parameters
function valid(a = 1, b = a * 2) {
console.log(a, b);
}
valid(); // 1 2
valid(5); // 5 103. Complex Expressions
Default parameters can be any expression, including ternary operators, logical operations, etc.:
function calculatePrice(basePrice, discount = basePrice > 100 ? 0.1 : 0.05) {
let finalPrice = basePrice * (1 - discount);
console.log(
`Original: $${basePrice}, Discount: ${
discount * 100
}%, Final: $${finalPrice}`
);
}
calculatePrice(150);
// Original: $150, Discount: 10%, Final: $135
calculatePrice(80);
// Original: $80, Discount: 5%, Final: $764. Object and Array Literals
function createConfig(options = { debug: false, retries: 3 }) {
console.log(`Debug mode: ${options.debug}`);
console.log(`Max retries: ${options.retries}`);
}
createConfig();
// Debug mode: false
// Max retries: 3
createConfig({ debug: true, retries: 5 });
// Debug mode: true
// Max retries: 5Combining with Destructuring
Default parameters are very powerful when combined with destructuring assignment:
Object Destructuring Parameters
function createUser({ name, age = 18, role = "user", active = true } = {}) {
// Note the = {} at the end provides default value for the entire parameter
return { name, age, role, active };
}
console.log(createUser({ name: "Alice" }));
// { name: "Alice", age: 18, role: "user", active: true }
console.log(createUser({ name: "Bob", age: 25, role: "admin" }));
// { name: "Bob", age: 25, role: "admin", active: true }
console.log(createUser());
// { name: undefined, age: 18, role: "user", active: true }Note the role of = {} at the end of the function parameters - it provides a default value for the entire parameter object. Without it, if createUser() is called without parameters, it will try to destructure undefined, causing an error:
// ❌ No default empty object
function createUserBad({ name, age = 18 }) {
return { name, age };
}
// createUserBad(); // TypeError: Cannot destructure property 'name' of 'undefined'
// ✅ Has default empty object
function createUserGood({ name, age = 18 } = {}) {
return { name, age };
}
createUserGood(); // { name: undefined, age: 18 }Array Destructuring Parameters
function processCoordinates([x = 0, y = 0, z = 0] = []) {
console.log(`Position: (${x}, ${y}, ${z})`);
}
processCoordinates([10, 20, 30]);
// Position: (10, 20, 30)
processCoordinates([5, 10]);
// Position: (5, 10, 0)
processCoordinates();
// Position: (0, 0, 0)Combining with Rest Parameters
Default parameters can be used with rest parameters, but rest parameters cannot have default values (because they are always an array):
function logMessages(level = "info", ...messages) {
console.log(`[${level.toUpperCase()}]`);
messages.forEach((msg) => console.log(` ${msg}`));
}
logMessages(); // [INFO] (no messages)
logMessages("error", "Connection failed", "Retrying...");
// [ERROR]
// Connection failed
// Retrying...
logMessages(undefined, "Using default level");
// [INFO]
// Using default levelPractical Application Scenarios
1. API Request Configuration
function fetchData(
url,
{ method = "GET", headers = {}, timeout = 5000, retries = 3 } = {}
) {
console.log(`Fetching ${url}`);
console.log(`Method: ${method}`);
console.log(`Timeout: ${timeout}ms`);
console.log(`Max retries: ${retries}`);
console.log(`Headers: ${JSON.stringify(headers)}`);
// Actual fetch logic...
}
// Use default values
fetchData("/api/users");
// Override partial default values
fetchData("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
});2. Data Formatting
function formatCurrency(
amount,
currency = "USD",
locale = "en-US",
options = {}
) {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: currency,
...options,
}).format(amount);
}
console.log(formatCurrency(1234.56));
// $1,234.56
console.log(formatCurrency(1234.56, "EUR", "de-DE"));
// 1.234,56 €
console.log(formatCurrency(1234.56, "JPY", "ja-JP"));
// ¥1,2353. Creating HTML Elements
function createElement(tag, content = "", attributes = {}, children = []) {
let attrs = Object.entries(attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(" ");
let attrsString = attrs ? " " + attrs : "";
let childrenHTML = children.join("");
return `<${tag}${attrsString}>${content}${childrenHTML}</${tag}>`;
}
console.log(createElement("h1", "Welcome"));
// <h1>Welcome</h1>
console.log(createElement("div", "", { class: "container", id: "main" }));
// <div class="container" id="main"></div>
console.log(
createElement("ul", "", {}, ["<li>Item 1</li>", "<li>Item 2</li>"])
);
// <ul><li>Item 1</li><li>Item 2</li></ul>4. Pagination and Filtering
function getUsers({
page = 1,
limit = 10,
sortBy = "createdAt",
order = "desc",
filters = {},
} = {}) {
console.log(`Fetching page ${page} (${limit} items per page)`);
console.log(`Sort by: ${sortBy} (${order})`);
console.log(`Filters: ${JSON.stringify(filters)}`);
// Actual database query...
}
getUsers(); // Use all default values
getUsers({ page: 2 }); // Only change page number
getUsers({ limit: 20, sortBy: "name", order: "asc" });
getUsers({ filters: { role: "admin", active: true } });Common Pitfalls and Best Practices
1. Be Careful When Default Values Are Reference Types
If default values are objects or arrays, they will be reused on each call (unless recalculated):
// ⚠️ Potential issue: sharing the same object
let defaultOptions = { debug: false };
function configure(options = defaultOptions) {
options.debug = true; // Modifies the default object
console.log(options);
}
configure(); // { debug: true }
defaultOptions.debug; // true - default object was modified!
// ✅ Better: create new object each time
function configureSafe(options = { debug: false }) {
options.debug = true;
console.log(options);
}
// Or use destructuring to create new object
function configureBest(options = {}) {
let config = { debug: false, ...options };
config.debug = true;
console.log(config);
}2. Evaluation Order of Default Values
Default values are evaluated from left to right, preceding parameters can be referenced by following ones:
function buildQuery(table, where = {}, limit = 10, offset = limit * 0) {
console.log(`SELECT * FROM ${table}`);
if (Object.keys(where).length > 0) {
console.log(`WHERE ${JSON.stringify(where)}`);
}
console.log(`LIMIT ${limit} OFFSET ${offset}`);
}
buildQuery("users");
// SELECT * FROM users
// LIMIT 10 OFFSET 0
buildQuery("posts", { published: true }, 20);
// SELECT * FROM posts
// WHERE {"published":true}
// LIMIT 20 OFFSET 03. Avoid Complex Default Value Expressions
Although default values can be complex expressions, overly complex ones reduce readability:
// ❌ Too complex
function process(
data,
transform = (data) =>
data
.filter((x) => x > 0)
.map((x) => x * 2)
.reduce((a, b) => a + b, 0)
) {
return transform(data);
}
// ✅ Clearer: extract complex logic
function defaultTransform(data) {
return data
.filter((x) => x > 0)
.map((x) => x * 2)
.reduce((a, b) => a + b, 0);
}
function processBetter(data, transform = defaultTransform) {
return transform(data);
}4. null vs undefined
Remember that null does not trigger default values:
function greet(name = "Guest") {
console.log(`Hello, ${name}!`);
}
greet(undefined); // Hello, Guest!
greet(null); // Hello, null!
// If you need to treat null as missing value, manually check
function greetBetter(name = "Guest") {
name = name === null ? "Guest" : name;
console.log(`Hello, ${name}!`);
}
greetBetter(null); // Hello, Guest!5. Required Parameters First, Optional Parameters Later
Place required parameters first, optional parameters with default values last:
// ✅ Good: required parameters first
function createArticle(
title,
content,
author = "Anonymous",
published = false
) {
return { title, content, author, published };
}
// ❌ Bad: required parameters last
function createArticleBad(
author = "Anonymous",
published = false,
title,
content
) {
// Need to pass preceding parameters even if you want to use default values
return { title, content, author, published };
}
createArticle("My Post", "Content here");
// { title: "My Post", content: "Content here", author: "Anonymous", published: false }
// createArticleBad is awkward to use
createArticleBad(undefined, undefined, "My Post", "Content here");Summary
Default parameters are an important feature introduced by ES6, making function parameter handling more elegant and robust.
Key points:
- Default parameters take effect when the parameter is
undefined, not including other falsy values likenull - Default values can be any valid JavaScript expression: literals, expressions, function calls, etc.
- Default values are calculated at each call time, not definition time
- Can reference preceding parameters, but not subsequent parameters
- Very powerful when combined with destructuring, but remember to provide default empty object/array
- Can be combined with rest parameters, but rest parameters themselves cannot have default values
- Avoid mutable reference types as default values to prevent accidental modifications
- Keep default value expressions simple, complex logic should be extracted outside the function
- Place required parameters first, optional parameters later
- Using object parameter patterns can handle multiple optional parameters more flexibly
Default parameters significantly improve code readability and robustness and are standard practice in modern JavaScript development. Combined with destructuring and rest parameters, you can create very flexible and elegant function interfaces.