JAVASCRIPT / FUNCTIONS
Parameters, arguments, and return values
Trace how a call copies arguments into fresh parameter bindings and how return sends one value back, so you can predict any function's result.
What you will learn
- Predict a call's result when arguments are missing, extra, or out of order
- Explain why mutating an object parameter escapes the function but reassigning it does not
- Use early return as a guard and read a call expression as the value it returned
- Send several results back by bundling them into one object or array
Understanding Parameters, arguments, and return values
The parameter list in a function header declares names, not values. Each call builds a fresh set of those bindings and fills them strictly by position: the first argument lands in the first parameter no matter what either one is called. If a call passes fewer arguments than there are parameters, the leftover parameters start out as undefined; if it passes more, the surplus values simply have no parameter name bound to them. The declared count is readable as makeLabel.length, which describes the signature and never changes from call to call.
Filling a parameter behaves like an assignment, so what travels in is a copy of the argument's value. For a number, string, or boolean the function owns that copy outright and can reassign the parameter without the caller ever noticing. For an object or array the value being copied is a reference, so the caller's variable and the parameter point at the same object: writing to a property through the parameter is visible outside, while assigning a whole new object to the parameter only redirects the local name. Separating those two operations, mutating what a reference points at versus replacing the reference, explains nearly every surprise about arguments.
return is the only channel back out. It ends the function immediately and makes the call expression evaluate to the value handed back, so const x = f() means x holds whatever f returned. A body that runs off its end, or hits a bare return with no value, produces undefined, which is why logging a result inside the function is not a substitute for returning it: the caller receives nothing it can use. Because one return carries exactly one value, multiple results have to ride together inside a single object or array.
function makeLabel(name, count) {
count = count * 2;
return name + ": " + count;
}
let items = 3;
console.log(makeLabel("apples", items));
console.log(items);
console.log(makeLabel("pears"));
console.log(makeLabel.length);A call copies each argument into a fresh local parameter binding, and return is the only path a value takes back to the caller.
Worked examples
Early return and falling off the end
Shows that return stops the function at once and that a body which finishes without returning yields undefined.
function firstNegative(numbers) {
for (const n of numbers) {
if (n < 0) return n;
}
}
console.log(firstNegative([4, -7, -9]));
console.log(firstNegative([1, 2]));
console.log(typeof firstNegative([1, 2]));Example explained
Line 1return n fires on the first match, so the loop stops and -9 is never inspected.
Line 2When the loop ends with no match, control leaves the body without a return and the call evaluates to undefined.
Line 3typeof confirms the result is the undefined value: a call expression always produces some value, even when nothing was returned.
Copied primitive, shared object
Contrasts mutating an object through a parameter with reassigning a parameter, and returns two results in one object.
function applyDiscount(order, percent) {
order.total = order.total - order.total * percent;
percent = 0;
return { total: order.total, percentUsed: percent };
}
const cart = { total: 200 };
let rate = 0.25;
const result = applyDiscount(cart, rate);
console.log(cart.total);
console.log(rate);
console.log(result.total, result.percentUsed);Example explained
Line 1order and cart hold the same reference, so writing order.total changes the object the caller still owns.
Line 2percent = 0 replaces only the local binding; rate outside is a copied number and stays 0.25.
Line 3The single return carries two results by packing them into one object literal.
Line 4result.percentUsed is 0 because it was read after the reassignment, showing the parameter really did change locally.
Important notes
Passing more arguments than a function declares is not an error, and passing fewer is not either; the extras are unbound and the missing ones are undefined.
A bare return; and no return at all both produce undefined, but return; also stops execution at that point, which is the reason to write it.
Common mistakes
Logging the result with console.log inside the function instead of returning it, so the call evaluates to undefined and the caller has nothing to store.
Writing return on one line and the value on the next; automatic semicolon insertion ends the statement immediately and the function returns undefined.
Expecting price = price * 2 inside the function to change the caller's price variable; primitives are copied, so the outer variable is untouched.
Passing arguments in the wrong order and assuming the names will sort it out; binding is positional, so the values land in the wrong parameters silently.
Try it yourself
Change, predict, then run
Write splitBill(total, people) that returns { each, remainder } using division and the remainder operator, then log splitBill(100, 3). Add total = 0 as the last line before the return and confirm your outer total variable still holds 100.
Open the JavaScript workspaceCheck your understanding
Given function tweak(list, n) { list.push(n); list = []; n = 99; return list.length; } with const data = [1, 2]; let num = 5;, what does console.log(tweak(data, num), data.length, num) print?
- 0 3 5
- 0 2 5
- 0 3 99
- 1 3 5
Show answer
push runs through the shared reference, so data really grows to three items; list = [] then points the parameter at a different array, making list.length 0 without undoing the push; n = 99 changes only the copied number, so num stays 5. The tempting 0 2 5 assumes the reassignment cancelled the push, but the mutation had already happened to the object the caller holds.