Positional Parameters
Parameters are matched to arguments by position. The first argument goes to the first parameter, and so on.
function introduce(firstName, lastName, age) {
return `${firstName} ${lastName}, age ${age}`;
}
console.log(introduce("Alice", "Smith", 30)); // Alice Smith, age 30
console.log(introduce("Bob", "Jones", 25)); // Bob Jones, age 25
// Extra arguments are silently ignored
console.log(introduce("Alice", "Smith", 30, "extra")); // OK
// Missing arguments become undefined
console.log(introduce("Alice")); // Alice undefined, age undefined
Default Parameter Values
Default values are used when an argument is undefined (missing or explicitly passed as undefined).
function createUser(name, role = "viewer", isActive = true) {
return { name, role, isActive };
}
console.log(createUser("Alice")); // {name:'Alice', role:'viewer', isActive:true}
console.log(createUser("Bob", "admin")); // {name:'Bob', role:'admin', isActive:true}
console.log(createUser("Carol", "editor", false)); // {name:'Carol', role:'editor', isActive:false}
// Passing undefined explicitly triggers the default
console.log(createUser("Dave", undefined, false)); // {name:'Dave', role:'viewer', isActive:false}
// Passing null does NOT trigger the default
console.log(createUser("Eve", null, true)); // {name:'Eve', role:null, isActive:true}
Defaults only kick in for undefined, not null. This is because null is an intentional "empty" value, while undefined means "nothing was provided".
Rest Parameters (...args)
The rest parameter syntax ...name collects all remaining arguments into a real Array. It must be the last parameter.
// Variadic sum function
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum()); // 0
// Mix: fixed params + rest
function logMessage(level, ...messages) {
const formatted = messages.join(" ");
console.log(`[${level.toUpperCase()}] ${formatted}`);
}
logMessage("info", "Server", "started", "on port 3000");
logMessage("error", "Connection", "refused");
The arguments Object (Legacy)
Before rest parameters existed, arguments was used to access all passed arguments. It's array-like but not a real Array, and it's unavailable in arrow functions.
// Legacy approach β avoid in modern code
function legacySum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(legacySum(1, 2, 3)); // 6
// Modern rest parameter approach (preferred)
function modernSum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
console.log(modernSum(1, 2, 3)); // 6
Destructured Parameters
You can destructure an object or array directly in the parameter list, extracting values by name.
// Object destructuring in parameters
function displayUser({ name, age, city = "Unknown" }) {
console.log(`${name}, ${age} years old, from ${city}`);
}
displayUser({ name: "Alice", age: 30, city: "London" });
displayUser({ name: "Bob", age: 25 }); // city defaults to "Unknown"
// Array destructuring in parameters
function processCoords([x, y, z = 0]) {
console.log(`x=${x}, y=${y}, z=${z}`);
}
processCoords([10, 20]);
processCoords([1, 2, 3]);
Named Parameters Pattern
When a function needs many optional configuration options, pass a single options object. This is the "named parameters" pattern β much cleaner than many positional arguments.
// Hard to read: what does each argument mean?
function createButton("Submit", true, "primary", "lg", null) { /* ... */ }
// Named parameters β self-documenting
function createButton({ text, disabled = false, variant = "default", size = "md", icon = null } = {}) {
return { text, disabled, variant, size, icon };
}
const btn = createButton({ text: "Submit", variant: "primary", size: "lg" });
console.log(btn);
// Can call with no args because of = {} default
const defaultBtn = createButton();
console.log(defaultBtn);
Pass by Value vs Pass by Reference
Primitives (numbers, strings, booleans) are passed by value β a copy is made. Objects and arrays are passed by reference β the function gets access to the original.
// Primitive β pass by value (copy)
function double(n) {
n = n * 2; // Modifies local copy only
return n;
}
let x = 5;
double(x);
console.log(x); // Still 5 β original unchanged
// Object β pass by reference
function addScore(player) {
player.score += 10; // Modifies original!
}
const p = { name: "Alice", score: 100 };
addScore(p);
console.log(p.score); // 110 β original changed!
// Avoid mutation: create and return new object
function addScoreImmutable(player) {
return { ...player, score: player.score + 10 };
}
const p2 = { name: "Bob", score: 100 };
const p2Updated = addScoreImmutable(p2);
console.log(p2.score); // 100 β original safe
console.log(p2Updated.score); // 110
Callbacks as Parameters
// Higher-order function accepting a callback
function processItems(items, transform) {
return items.map(transform);
}
const prices = [10, 20, 30];
const withTax = processItems(prices, price => price * 1.2);
console.log(withTax); // [12, 24, 36]
// Callback with error-first pattern (Node.js style)
function fetchData(url, onSuccess, onError) {
if (!url) {
onError(new Error("URL required"));
return;
}
onSuccess({ data: "mock response from " + url });
}
fetchData("https://api.example.com",
(res) => console.log("Got:", res.data),
(err) => console.log("Error:", err.message)
);
ποΈ Practical Exercise
Write a function formatCurrency(amount, { currency = 'USD', decimals = 2, symbol = '$' } = {}) using the named parameters pattern. It should return a string like "$1,234.56". Test it with:
formatCurrency(1234.5)formatCurrency(1000, { currency: 'EUR', symbol: 'β¬' })formatCurrency(500, { decimals: 0 })
π₯ Challenge Exercise
Build a function pipeline(...fns) that accepts any number of functions as rest parameters and returns a new function. The returned function passes its input through all the provided functions in order (output of one becomes input of the next). Test with: pipeline(x => x * 2, x => x + 1, x => x ** 2)(3) β should produce 49.
π Summary
- Parameters are positional; extra arguments are ignored, missing ones are
undefined. - Default values fill in for
undefined(notnull). - Rest parameters (
...args) collect remaining arguments into a real Array. - Avoid the legacy
argumentsobject β use rest params instead. - Destructured parameters let you pull values by name directly in the signature.
- Named parameters (options object) are cleaner than many positional args.
- Primitives are passed by value; objects/arrays are passed by reference.
Interview Questions
- What is the difference between rest parameters and the
argumentsobject? - When does a default parameter value trigger? Does
nulltrigger a default? - Explain pass-by-value vs pass-by-reference with examples.
- What is the named parameters pattern and why is it useful?
- How do you handle optional parameters in a clean way?
Frequently Asked Questions
Technically yes, but it's unhelpful. To use the default, you must pass undefined explicitly: fn(undefined, "value"). Always place parameters with defaults after required ones.
Yes. Rest parameters work in all function types including arrow functions. However, the arguments object is NOT available in arrow functions β another reason to prefer rest params.
Arity is the number of parameters a function expects. You can check it with fn.length. Rest parameters and parameters with defaults don't count toward the length property.