Skip to main content

📥 Parameters, Arguments, and Return Values

A function is a machine: you feed it inputs, it does work, and it hands back a result. This lesson is all about that flow of data — how to define flexible inputs, how JavaScript passes values in, and how to shape what comes back out.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Distinguish parameters (placeholders) from arguments (actual values)
  • Use default parameters, rest parameters, and destructuring to write flexible signatures
  • Explain pass-by-value vs. pass-by-reference and avoid accidental mutation
  • Apply useful return-value patterns (status objects, multiple values, method chaining)
  • Separate pure calculations from side effects

Estimated Time: 35–45 minutes  •  Difficulty: Intermediate

Hands-on: Write a getStatistics function that returns several values at once.

In This Lesson

Parameters vs. Arguments

These two words are often used interchangeably, but they mean different things — and the distinction clears up a lot of confusion later.

  • Parameters are the named variables in the function definition. They are placeholders.
  • Arguments are the actual values you pass when you call the function.
  • Return value is the data the function hands back to wherever it was called.
A function as an input-process-output machine Parameters flow into a processing box that multiplies width by height, and the result area flows out as the return value. Parameters width, height Process width * height Return value area
Figure 1 — Parameters go in, the body processes them, a return value comes out.
function add(a, b) {   // a and b are PARAMETERS (placeholders)
  return a + b;
}

add(5, 3);            // 5 and 3 are ARGUMENTS (actual values) → returns 8
💡 A parking analogy: Parameters are labelled parking spaces; arguments are the actual cars that pull into them. Inside the function you refer to the space by its label, whichever car happens to be parked there.

Default Parameters

Default parameters let you supply a fallback value used whenever an argument is missing or undefined. This removes a pile of manual "if it wasn't passed, set it" checks.

function createUser(name = 'Anonymous', role = 'User', active = true) {
  return { name, role, active };
}

createUser();                     // { name: 'Anonymous', role: 'User', active: true }
createUser('Sarah');              // { name: 'Sarah', role: 'User', active: true }
createUser('Michael', 'Admin');   // { name: 'Michael', role: 'Admin', active: true }

Defaults can be expressions, including calls to other functions — evaluated only when needed:

function randomId() {
  return 'user_' + Math.floor(Math.random() * 10000);
}

function createAccount(username = randomId(), verified = false) {
  return { username, verified };
}

createAccount();            // { username: 'user_8423', verified: false }
createAccount('jane_doe');  // { username: 'jane_doe', verified: false }

💡 The "options object" pattern

Combining defaults with a destructured object parameter gives you named, optional arguments in any order. The = {} at the end means the whole object is itself optional:

function createProduct({
  name = 'Unnamed Product',
  price = 0,
  category = 'Miscellaneous',
  inStock = true
} = {}) {
  return { name, price, category, inStock };
}

createProduct({ name: 'T-Shirt', price: 19.99 });
// { name: 'T-Shirt', price: 19.99, category: 'Miscellaneous', inStock: true }
createProduct(); // all defaults, no error

Rest Parameters

Rest parameters (...name) collect an unlimited number of arguments into a real array. They're the modern replacement for the awkward arguments object.

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

sum(1, 2);          // 3
sum(1, 2, 3, 4, 5); // 15
sum();              // 0

A rest parameter must be last, and you can put ordinary parameters before it:

function createTeam(teamName, leader, ...members) {
  return { name: teamName, leader, members, size: members.length + 1 };
}

createTeam('Avengers', 'Iron Man', 'Captain America', 'Thor', 'Hulk');
// { name: 'Avengers', leader: 'Iron Man',
//   members: ['Captain America', 'Thor', 'Hulk'], size: 4 }

⚠️ Rest parameters vs. the arguments object

The legacy arguments object is array-like but not a true array, so it lacks map, reduce, etc. Rest parameters are a genuine array and work in arrow functions. Prefer them.

// ❌ Old
function oldSum() {
  return Array.from(arguments).reduce((s, n) => s + n, 0);
}
// ✅ Modern
function newSum(...nums) {
  return nums.reduce((s, n) => s + n, 0);
}

Parameter Destructuring

Destructuring unpacks properties from an object (or elements from an array) straight into named parameters. It makes a function's expected shape self-documenting.

// Without destructuring
function displayUser(user) {
  console.log(`${user.name} — ${user.email}`);
}

// With object destructuring
function displayUser({ name, email }) {
  console.log(`${name} — ${email}`);
}

displayUser({ name: 'Alex', email: 'alex@example.com', country: 'Canada' });
// "Alex — alex@example.com"  (country is simply ignored)

Array destructuring works too, and you can mix in defaults:

function coordinateInfo([x, y, z = 0]) {
  return `X=${x}, Y=${y}, Z=${z}`;
}

coordinateInfo([10, 20]);     // "X=10, Y=20, Z=0"
coordinateInfo([5, 15, 25]);  // "X=5, Y=15, Z=25"

📖 Where you'll see this constantly: React

Destructured props are idiomatic in React components — it's the same feature you just learned:

function UserProfile({ user, isEditable = false, theme = 'light' }) {
  return `<div class="profile profile--${theme}">${user.name}</div>`;
}

By Value vs. By Reference

How JavaScript passes an argument depends on its type — and getting this wrong is a classic source of "why did my object change?" bugs.

flowchart TD A[Argument] --> B{What type?} B -->|Primitive: number, string, boolean| C[Passed by VALUE
function gets a copy] B -->|Object, array, function| D[Passed by REFERENCE
function shares the original]

Primitives are copied

function modify(n) {
  n = n * 2;
  console.log('inside:', n); // 20
}

let x = 10;
modify(x);
console.log('after:', x); // 10 — unchanged

Objects and arrays are shared

function modify(obj) {
  obj.value = obj.value * 2; // mutates the ORIGINAL
}

const data = { value: 10 };
modify(data);
console.log(data.value); // 20 — changed!

✅ Reassignment is not mutation

Reassigning the parameter to a brand-new object does not affect the caller — you've only pointed the local name elsewhere. To update safely without touching the input, return a copy using the spread operator:

function doubleImmutably(obj) {
  return { ...obj, value: obj.value * 2 }; // new object
}

const data = { value: 10, label: 'test' };
const next = doubleImmutably(data);
console.log(data.value); // 10 (unchanged)
console.log(next.value); // 20 (new object)

Return Values & Patterns

The return statement hands a value back and immediately ends the function. A function with no return (or one that falls off the end) returns undefined.

function isAdult(age) {
  return age >= 18; // returns a boolean
}

function logMessage(msg) {
  console.log(msg); // no return → yields undefined
}

console.log(logMessage('hi')); // logs "hi", then prints undefined

Early returns (guard clauses) let you validate up front and keep the happy path un-indented:

function divide(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') return 'Numbers only';
  if (b === 0) return 'Cannot divide by zero';
  return a / b;
}

Returning several values with an object

function getStatistics(numbers) {
  const sum = numbers.reduce((t, n) => t + n, 0);
  const count = numbers.length;
  return {
    sum,
    count,
    average: count ? sum / count : 0,
    min: Math.min(...numbers),
    max: Math.max(...numbers)
  };
}

const { average, max } = getStatistics([5, 10, 15, 20, 25]);
console.log(average, max); // 15 25

The status-object pattern

For operations that can fail, return an object describing what happened rather than throwing or returning a bare value:

function createUser(data) {
  if (!data.username || !data.email) {
    return { success: false, error: 'Username and email are required', data: null };
  }
  return { success: true, error: null, data: { id: Date.now(), ...data } };
}

const result = createUser({ username: 'alice', email: 'a@ex.com' });
if (result.success) console.log('Created', result.data);
else console.error(result.error);

Method chaining

Return the object itself (this) so calls can be chained fluently — the pattern behind query builders and many libraries:

function query() {
  return {
    table: '', conditions: [],
    from(t) { this.table = t; return this; },
    where(c) { this.conditions.push(c); return this; },
    build() {
      let sql = `SELECT * FROM ${this.table}`;
      if (this.conditions.length) sql += ` WHERE ${this.conditions.join(' AND ')}`;
      return sql;
    }
  };
}

const sql = query().from('users').where('age >= 18').where('active = 1').build();
console.log(sql); // SELECT * FROM users WHERE age >= 18 AND active = 1

Returns vs. Side Effects

A function influences the world in two ways: by returning a value, or by causing a side effect — changing something outside itself (updating the DOM, writing a file, logging, mutating an external variable).

A pure function returns a result based only on its inputs and has no side effects. Pure functions are easier to test, predict, and reuse.

Pure functionImpure function
Same input → same output, alwaysOutput can vary between calls
No external changesModifies external state / does I/O
Trivial to unit-testNeeds mocks and setup to test

You can't avoid side effects entirely — apps must eventually touch the screen and network. The practical rule is to isolate them: keep most functions pure and push side effects to a thin edge.

// ✅ Pure: just calculates
function calculateTotal(items) {
  return items.reduce((t, item) => t + item.price * item.quantity, 0);
}

// Side effect kept separate
function displayTotal(total) {
  document.getElementById('total').textContent = `$${total.toFixed(2)}`;
}

const items = [{ price: 9.99, quantity: 3 }, { price: 14.95, quantity: 2 }];
displayTotal(calculateTotal(items));

Hands-on Exercise

🏋️ Flexible Averager

Objective: Practice rest parameters, defaults, and multi-value returns.

Instructions:

  1. Write analyze(...values) using a rest parameter.
  2. Return an object with count, sum, average, min, and max.
  3. If called with no values, return sensible zeros instead of NaN.
  4. Add a second function formatReport(stats, { currency = '$' } = {}) using a destructured options object with a default.
💡 Hint

Math.min() / Math.max() return Infinity/-Infinity for an empty list — guard the empty case with an early return before you spread.

✅ Sample solution
function analyze(...values) {
  if (values.length === 0) {
    return { count: 0, sum: 0, average: 0, min: 0, max: 0 };
  }
  const sum = values.reduce((t, n) => t + n, 0);
  return {
    count: values.length,
    sum,
    average: sum / values.length,
    min: Math.min(...values),
    max: Math.max(...values)
  };
}

function formatReport(stats, { currency = '$' } = {}) {
  return `Count ${stats.count} | Avg ${currency}${stats.average.toFixed(2)} `
       + `| Min ${currency}${stats.min} | Max ${currency}${stats.max}`;
}

const stats = analyze(5, 10, 15, 20, 25);
console.log(formatReport(stats));            // Count 5 | Avg $15.00 | Min $5 | Max $25
console.log(formatReport(analyze()));        // Count 0 | Avg $0.00 | Min $0 | Max $0

🎯 Quick Quiz

Question 1: In function add(a, b) { return a + b; } called as add(5, 3), what are 5 and 3?

Question 2: A function receives an object and does obj.value = 99. After the call, the caller's object…

Question 3: What does a ...rest parameter give you?

Summary & What's Next

🎉 Key Takeaways

  • Parameters are placeholders; arguments are the values you pass.
  • Default, rest, and destructured parameters make signatures flexible and self-documenting.
  • Primitives pass by value; objects and arrays pass by reference — copy before mutating.
  • Shape return values deliberately (status objects, multiple values, chaining) and keep pure logic separate from side effects.

📚 Further Reading

🚀 What's Next?

You now control what goes in and out of a function. Next we go inside — how JavaScript decides which variables a function can see, and how the call stack and this work, in Function Scope and Execution Context.