AlgoInsight

Free lesson · JavaScript Basic to Advance

Parameters: Defaults, Rest, Destructuring

Easy5 min read

Modern Parameter Features

Modern JavaScript gives you three powerful parameter features that make functions cleaner and more flexible: default parameters, rest parameters, and destructured parameters. All three were added in ES6 and are standard in modern code.

// Default parameter — fallback value if argument isn't provided
const greet = (name = "stranger") => `Hello, ${name}`;

// Rest parameter — gather extra arguments into an array
const sum = (...numbers) => numbers.reduce((total, n) => total + n, 0);

// Destructured parameter — extract values from an object or array
const describeUser = ({ name, age }) => `${name} is ${age} years old`;

console.log(greet());
console.log(greet("Olena"));
console.log(sum(1, 2, 3, 4, 5));
console.log(describeUser({ name: "Olena", age: 35 }));

Each feature solves a real problem. Defaults handle the case where a caller might omit an argument. Rest parameters let you accept any number of arguments. Destructuring lets you pull pieces out of objects or arrays directly in the parameter list. Together, they make function signatures much more expressive.

This Topic covers all three. They're independent features — you'll often use them together — but they're each worth understanding individually first.

Default Parameters

Default parameters specify a fallback value to use when an argument isn't provided. The syntax is parameter = defaultValue in the parameter list:

function greet(name = "stranger") {
  return `Hello, ${name}`;
}

console.log(greet());           // "Hello, stranger"
console.log(greet("Olena"));    // "Hello, Olena"
console.log(greet(undefined));  // "Hello, stranger" — undefined triggers the default
console.log(greet(null));       // "Hello, null" — null does NOT trigger the default

The default only kicks in when the argument is undefined — either because no argument was passed at all, or because undefined was explicitly passed. Other falsy values (null, 0, "", false) don't trigger the default. This is a deliberate design choice — null means "deliberately empty," and JavaScript respects that.

Defaults can be any expression, including function calls or references to other parameters:

function createUser(name, role = "member", createdAt = Date.now()) {
  return { name, role, createdAt };
}

console.log(createUser("Olena"));
console.log(createUser("Nikhil", "admin"));
console.log(createUser("Emeka", "admin", 0));

Each parameter can have its own default. They're evaluated left-to-right when the function is called, which means later parameters can reference earlier ones:

function range(start, end, step = end > start ? 1 : -1) {
  return `From ${start} to ${end}, stepping by ${step}`;
}

console.log(range(1, 10));    // step defaults to 1
console.log(range(10, 1));    // step defaults to -1
console.log(range(1, 10, 2)); // step explicitly given

The step default uses both start and end to compute itself. This is a powerful pattern — you can compute sensible defaults from other inputs.

Before ES6, the way to handle defaults was awkward:

// Old way (still works, but verbose)
function oldGreet(name) {
  name = name || "stranger";   // or: name = name === undefined ? "stranger" : name
  return `Hello, ${name}`;
}

console.log(oldGreet());
console.log(oldGreet("Olena"));

This old pattern using || has a bug — it treats "", 0, and false as missing, even when they were deliberately passed. Default parameters fix this cleanly.

Interactive animation: What triggers a default - only undefined does, while null and an empty string go straight through.

Rest Parameters

A rest parameter collects all remaining arguments into an array. The syntax is ...parameterName and it must be the last parameter:

function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum());                 // 0
console.log(sum(1));                // 1
console.log(sum(1, 2, 3));          // 6
console.log(sum(1, 2, 3, 4, 5));    // 15

Inside the function, numbers is a regular array containing whatever was passed. You can use all array methods on it — reduce, map, filter, etc.

Rest parameters can be combined with regular parameters:

function greetEveryone(greeting, ...names) {
  return names.map(name => `${greeting}, ${name}`).join(". ");
}

console.log(greetEveryone("Hello", "Olena", "Nikhil", "Emeka"));

The first argument fills greeting. All remaining arguments collect into names. This is the modern way to handle "variable number of arguments" — much cleaner than the old arguments object that traditional functions had access to.

Interactive animation: Rest gathers, spread scatters - the same three dots collect arguments in a signature and spread an array at a call.

The Rest Parameter vs the Old arguments Object

Traditional functions have access to an arguments object that contains all arguments passed to the function. Arrow functions don't. Modern code prefers rest parameters because they're explicit, array-like, and work everywhere:

// Old: arguments object (only in traditional functions)
function oldSum() {
  // 'arguments' is array-like but not a real array
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

// Modern: rest parameter (works in arrow functions too)
const newSum = (...nums) => nums.reduce((total, n) => total + n, 0);

console.log(oldSum(1, 2, 3));
console.log(newSum(1, 2, 3));

The rest parameter version is clearer about what's happening, works in arrow functions, and gives you a real array to work with instead of the awkward "array-like" arguments object.

Destructuring Parameters

Destructuring lets you extract values directly in the parameter list, instead of unpacking them inside the function body.

Object destructuring — pull named properties out of an object argument:

// Without destructuring
function describeUserOld(user) {
  const name = user.name;
  const age = user.age;
  return `${name} is ${age} years old`;
}

// With destructuring
function describeUser({ name, age }) {
  return `${name} is ${age} years old`;
}

const user = { name: "Olena", age: 35, role: "Product" };
console.log(describeUserOld(user));
console.log(describeUser(user));

The destructured version is shorter and makes the function's expected inputs visible in the signature. Looking at the function declaration alone, you can immediately see it needs name and age from the argument object.

You can also rename, set defaults, and destructure nested objects in the parameter list:

function describeUserFull({ name, age = 0, address: { city = "unknown" } = {} } = {}) {
  return `${name} is ${age} years old, from ${city}`;
}

console.log(describeUserFull({ name: "Olena", age: 35, address: { city: "Redmond" } }));
console.log(describeUserFull({ name: "Nikhil" }));
console.log(describeUserFull());

That signature has a lot going on, but it's all features we've covered. name, age, and address are destructured from the argument. age defaults to 0. address further destructures to extract city, which defaults to "unknown" if the nested object is missing. The outermost = {} provides a default for the entire argument, so calling without arguments doesn't crash.

This is heavy. For most functions, simpler destructuring is enough. But knowing the full syntax is helpful when you read complex codebases.

Array destructuring — pull positional values out of an array argument:

function distance([x1, y1], [x2, y2]) {
  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}

console.log(distance([0, 0], [3, 4]));   // 5

The function takes two array arguments and destructures them by position. [x1, y1] extracts the first two values of the first array. Same for the second.

Interactive animation: Positional vs options object - one destructured object argument names every setting the caller passes.

Combining the Three Features

Default, rest, and destructuring work together. The most expressive function signatures use all three:

function createPost(
  { title, body = "", tags = [] } = {},
  ...attachments
) {
  return {
    title,
    body,
    tags,
    attachmentCount: attachments.length
  };
}

console.log(createPost({ title: "My Post", body: "Content here" }, "img1.png", "img2.png"));
console.log(createPost({ title: "Just a title" }));
console.log(createPost());

The function takes a configuration object (with destructured properties and defaults) plus any number of additional attachment arguments. The whole signature describes the function's contract clearly: "I expect a post config and any number of attachments."

graph LR
    A[Function Parameters] --> B[Default<br/>fallback value]
    A --> C[Rest<br/>collects extras]
    A --> D[Destructured<br/>extracts from object/array]

    style B fill:#10b981,color:#fff
    style C fill:#7c3aed,color:#fff
    style D fill:#3b82f6,color:#fff
This diagram summarizes the three modern parameter features. Default parameters provide a fallback value when an argument is not provided. Rest parameters collect extra arguments into an array, used when a function accepts a variable number of inputs. Destructured parameters extract values directly from an object or array argument, making the function's expected inputs visible in the signature.

Common Pitfalls

Interactive animation: Per-field defaults plus the = {} guard, so calling with no argument at all no longer throws.

Warning: The parameter traps.

Forgetting that defaults only trigger on undefined. Passing null, 0, or "" doesn't trigger the default. If your default should apply for any "missing or empty" case, use nullish coalescing inside the function instead (Module 10).

Rest parameter ordering. Rest must be the last parameter. function f(...nums, last) is a syntax error. The rest parameter has to come at the end so JavaScript knows when "extra" arguments start.

Destructuring undefined. Trying to destructure undefined throws an error. function f({ name }) { ... }; f(); throws because there's no object to destructure. Use a default like function f({ name } = {}) { ... } to allow the function to be called without arguments.

Why This Matters in Interviews

Interview: Modern parameter features are expected fluency in JavaScript interviews. A candidate writing function f(opts) { const name = opts.name; } instead of function f({ name }) signals they haven't fully adopted ES6+. Destructuring in parameter lists is one of the most common modern idioms.

Interviewers also sometimes ask about the difference between rest parameters and the old arguments object — a question that tests whether you know modern syntax exists, and whether you understand why it's preferred.

Topic 5.8 — Interview Lens: Functions and Higher-Order Patterns — covers the deeper interview-prep treatment.

Try It

Exercise: Write a function called formatMessage with these features, using all three parameter techniques covered in this Topic:

  1. Takes a config object with subject and body (use destructuring)
  2. subject should default to "No subject" if not provided
  3. body should default to an empty string
  4. Takes any number of recipients after the config object (use rest)
  5. Returns a formatted string with the subject, body, and a count of recipients

Then call the function:

  • With a config and three recipients
  • With just a config object (no recipients)
  • With nothing at all

Each call should work without throwing an error. This combines all three features into a realistic function signature.

Back to JavaScript Basic to Advance