Episode 1 — Fundamentals / 1.20 — Functions

1.20.b — Parameters and Arguments

In one sentence: Parameters are the named placeholders in a function's definition; arguments are the actual values you pass when you call it — and JavaScript gives you default values, rest parameters, destructuring, and the legacy arguments object to build flexible, self-documenting function signatures.

Navigation: ← 1.20.a — Declaration vs Expression · 1.20.c — Return Values and Scope →


1. Parameters vs arguments — the vocabulary

//           parameters ↓
function add(a, b) {
  return a + b;
}

//       arguments ↓
add(3, 5);
TermWhereWhat
ParameterFunction definitionA named variable that will receive a value
ArgumentFunction callThe actual value passed to a parameter

Mismatch rules:

  • Fewer arguments than parameters — missing params are undefined.
  • More arguments than parameters — extras are silently ignored (but accessible via arguments or rest).
function demo(a, b) {
  console.log(a, b);
}

demo(1);         // 1 undefined
demo(1, 2, 3);   // 1 2  — the 3 is ignored

2. Default parameters

Introduced in ES6, default parameters provide a fallback when an argument is undefined (or not passed):

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

console.log(greet());          // "Hello, World!"
console.log(greet("Alice"));   // "Hello, Alice!"
console.log(greet(undefined)); // "Hello, World!" — undefined triggers default
console.log(greet(null));      // "Hello, null!"  — null does NOT trigger default

Defaults can be expressions

function createId(prefix = "id", num = Date.now()) {
  return `${prefix}_${num}`;
}

Defaults can reference earlier parameters

function createElement(tag, className = tag) {
  return `<${tag} class="${className}"></${tag}>`;
}

console.log(createElement("div"));           // <div class="div"></div>
console.log(createElement("div", "card"));   // <div class="card"></div>

Defaults can call functions

function getDefaultName() {
  return "Anonymous";
}

function greetUser(name = getDefaultName()) {
  console.log(`Welcome, ${name}`);
}

greetUser();        // "Welcome, Anonymous"
greetUser("Bob");   // "Welcome, Bob"

Pre-ES6 pattern (for reference)

// Old style — still seen in legacy code
function greet(name) {
  name = name || "World";        // Falsy check — breaks for "", 0, false
  name = name !== undefined ? name : "World"; // Safer
  return `Hello, ${name}!`;
}

3. Rest parameters (...args)

The rest parameter collects all remaining arguments into a real Array:

function sum(...numbers) {
  let total = 0;
  for (const n of numbers) {
    total += n;
  }
  return total;
}

console.log(sum(1, 2, 3));       // 6
console.log(sum(10, 20, 30, 40)); // 100

Rules for rest parameters

  1. Must be the last parameter.
  2. Only one rest parameter per function.
  3. Produces a real Array (unlike arguments).
function logInfo(level, ...messages) {
  console.log(`[${level}]`, ...messages);
}

logInfo("ERROR", "File not found", "path: /data"); 
// [ERROR] File not found path: /data

Rest vs spread — same syntax, different context

// REST — in parameter list: collects into array
function demo(...args) {
  console.log(args); // [1, 2, 3]
}

// SPREAD — in function call: expands array into arguments
const nums = [1, 2, 3];
demo(...nums);

4. The arguments object (legacy)

Before rest parameters, JavaScript provided an array-like arguments object inside every (non-arrow) function:

function oldSum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(oldSum(1, 2, 3)); // 6

arguments vs rest parameters

FeatureargumentsRest (...args)
TypeArray-like (no .map, .filter)Real Array
Arrow functionsNot availableAvailable
Named paramsContains all args (named + extra)Contains only remaining args
Modern usageLegacy — avoid in new codePreferred

Converting arguments to a real array

function legacy() {
  const args = Array.from(arguments);  // or [...arguments]
  return args.map(x => x * 2);
}

console.log(legacy(1, 2, 3)); // [2, 4, 6]

5. Passing primitives vs objects

JavaScript is pass-by-value — but for objects, the "value" is a reference (memory address). This leads to behavior that looks like "pass by reference" for objects.

Primitives — passed by value (copy)

function increment(num) {
  num = num + 1;
  console.log("Inside:", num); // 11
}

let x = 10;
increment(x);
console.log("Outside:", x);   // 10 — unchanged

Reassigning num inside the function does not affect the outer x.

Objects — reference is passed by value

function addProperty(obj) {
  obj.newProp = "added"; // Mutates the original
}

const user = { name: "Alice" };
addProperty(user);
console.log(user); // { name: "Alice", newProp: "added" } — mutated!

The function receives a copy of the reference — both the outer user and the inner obj point to the same object in memory.

Reassigning the parameter does NOT affect the original

function replace(obj) {
  obj = { name: "Bob" }; // Reassigns LOCAL reference
  console.log("Inside:", obj); // { name: "Bob" }
}

const user = { name: "Alice" };
replace(user);
console.log("Outside:", user); // { name: "Alice" } — unchanged

Summary table

What's passedMutation inside functionReassignment inside function
Primitive (number, string, boolean)N/A (immutable)Does not affect outer
Object (object, array, function)Affects outer objectDoes not affect outer reference

Defensive pattern — avoid surprise mutations

function processUser(user) {
  const copy = { ...user }; // Shallow copy
  copy.processed = true;
  return copy;
}

const original = { name: "Alice" };
const result = processUser(original);
console.log(original.processed); // undefined — safe
console.log(result.processed);   // true

6. Destructured parameters

You can destructure objects and arrays directly in the parameter list for cleaner, self-documenting code:

Object destructuring

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

const user = createUser({ name: "Alice", age: 30 });
console.log(user);
// { name: "Alice", age: 30, role: "user", createdAt: ... }

With default for the whole parameter (prevents error when called with no argument)

function createUser({ name = "Anon", age = 0, role = "user" } = {}) {
  return { name, age, role };
}

console.log(createUser());              // { name: "Anon", age: 0, role: "user" }
console.log(createUser({ name: "Bob" })); // { name: "Bob", age: 0, role: "user" }

Array destructuring

function getFirstAndLast([first, ...rest]) {
  const last = rest.length > 0 ? rest[rest.length - 1] : first;
  return { first, last };
}

console.log(getFirstAndLast([10, 20, 30])); // { first: 10, last: 30 }

Nested destructuring

function printAddress({ address: { city, zip } }) {
  console.log(`${city}, ${zip}`);
}

printAddress({ address: { city: "NYC", zip: "10001" } });
// "NYC, 10001"

7. Parameter validation patterns

Guard clause (early return)

function divide(a, b) {
  if (b === 0) {
    throw new Error("Cannot divide by zero");
  }
  return a / b;
}

Type checking

function repeat(str, times) {
  if (typeof str !== "string") {
    throw new TypeError(`Expected string, got ${typeof str}`);
  }
  if (typeof times !== "number" || times < 0) {
    throw new TypeError("times must be a non-negative number");
  }
  return str.repeat(times);
}

Required parameter trick

function required(paramName) {
  throw new Error(`Missing required parameter: ${paramName}`);
}

function createUser(name = required("name"), email = required("email")) {
  return { name, email };
}

createUser("Alice");               // Error: Missing required parameter: email
createUser("Alice", "a@b.com");    // { name: "Alice", email: "a@b.com" }

Config object pattern

When a function needs many options, use an object instead of positional parameters:

// Bad — what do false, true, 3 mean?
createChart("sales", false, true, 3);

// Good — self-documenting
createChart({
  title: "sales",
  showLegend: false,
  animate: true,
  columns: 3,
});

function createChart({ title, showLegend = true, animate = false, columns = 1 } = {}) {
  console.log(`Chart: ${title}, legend: ${showLegend}, animate: ${animate}, cols: ${columns}`);
}

8. Real examples — flexible function signatures

Example 1: Flexible event logger

function log(message, { level = "info", timestamp = true, prefix = "" } = {}) {
  const time = timestamp ? `[${new Date().toISOString()}] ` : "";
  const pre = prefix ? `${prefix}: ` : "";
  console.log(`${time}${level.toUpperCase()} ${pre}${message}`);
}

log("Server started");
log("Disk full", { level: "error" });
log("Cache hit", { level: "debug", prefix: "CACHE" });

Example 2: Variadic math functions

function min(...values) {
  if (values.length === 0) throw new Error("At least one value required");
  let result = values[0];
  for (const v of values) {
    if (v < result) result = v;
  }
  return result;
}

console.log(min(5, 3, 8, 1)); // 1

// Spread an array into the rest parameter
const scores = [88, 92, 76, 95];
console.log(min(...scores));   // 76

Example 3: Function with mixed required and optional params

function sendEmail(to, subject, { cc = [], bcc = [], html = false } = {}) {
  console.log(`To: ${to}`);
  console.log(`Subject: ${subject}`);
  if (cc.length)  console.log(`CC: ${cc.join(", ")}`);
  if (bcc.length) console.log(`BCC: ${bcc.join(", ")}`);
  console.log(`Format: ${html ? "HTML" : "plain text"}`);
}

sendEmail("alice@example.com", "Hello");
sendEmail("bob@example.com", "Meeting", {
  cc: ["carol@example.com"],
  html: true,
});

Key takeaways

  1. Parameters are in the definition; arguments are in the call — extra arguments are ignored, missing ones become undefined.
  2. Default parameters (= value) provide fallbacks and can reference earlier params or call functions.
  3. Rest parameters (...args) collect remaining arguments into a real Array — always the last parameter.
  4. The arguments object is legacy — prefer rest parameters in modern code.
  5. Primitives are copied by value; objects pass a copy of the reference — mutations inside affect the original, reassignments do not.
  6. Destructured parameters make function signatures self-documenting — always provide = {} default for optional config objects.
  7. Validate parameters early with guard clauses, type checks, or the required-parameter trick.

Explain-It Challenge

Explain without notes:

  1. Why does passing a number to a function and reassigning it inside not change the outer variable?
  2. Why does passing an object and adding a property inside change the outer object?
  3. What is the difference between ...args in a parameter list vs ...arr in a function call?
  4. Why should you prefer rest parameters over the arguments object?

Navigation: ← 1.20.a — Declaration vs Expression · 1.20.c — Return Values and Scope →