The parameters a function accepts and the value it gives back are its contract with the caller. JavaScript is loose about that contract — missing arguments become undefined, extra arguments are ignored, and a function with no return quietly produces nothing — which is why it pays to understand the defaults and to design functions that fail loudly when they are misused.
What is the basic contract between caller and function?
Parameters are the names in the declaration. Arguments are the values you pass at the call site. The values are matched by position, and the function body sees the parameter names bound to them.
function describe(name, role) {
return `${name} works as ${role}`;
}
console.log(describe("Ada", "engineer")); // "Ada works as engineer"
console.log(describe("Ada", "teacher", "extra")); // "Ada works as teacher"
Two things happened in the second call. The third argument was ignored entirely, because the function only declared two parameters. And the arguments were matched strictly by position — there is no way to skip the first one and supply the second.
What happens when you pass too few arguments?
The missing ones are undefined. Nothing throws, nothing warns. The function simply runs with undefined where a value was expected.
function describe(name, role) {
return `${name} works as ${role}`;
}
console.log(describe("Ada", "engineer")); // "Ada works as engineer"
console.log(describe("Ada")); // "Ada works as undefined"
console.log(describe()); // "undefined works as undefined"
That third line is rarely what anyone wants, and it fails quietly — the template literal converts undefined to the text "undefined" and the program carries on. Two tools prevent this class of bug. The first is a default parameter, covered in detail in the next lesson of this module. The second is a guard at the top of the function:
function withTax(amount, rate) {
if (typeof amount !== "number" || Number.isNaN(amount)) {
throw new TypeError("amount must be a number");
}
return Math.round(amount * (1 + rate) * 100) / 100;
}
console.log(withTax(100, 0.18)); // 118
try {
withTax("100", 0.18);
} catch (error) {
console.log("caught:", error.constructor.name, "-", error.message);
}
The guard turns a silent wrong answer into an immediate, named error. That is usually the better trade: a crash at the point of the mistake is far cheaper than a wrong total discovered days later.
Do parameters change the caller's variables?
No. A parameter is a local binding. When you pass a primitive, the function gets a copy of the value. When you pass an object, the function gets a reference to the same object — which is a different story, covered in the objects module of this tutorial.
function applyDiscount(total, discount) {
total = total - discount; // changes the local copy only
return total;
}
const cart = { total: 500 };
console.log(applyDiscount(cart.total, 50)); // 450
console.log(cart.total); // 500 — untouched
The assignment inside the function rebinds the parameter, which has no effect outside it. A common confusion is that cart.total went down to 450 — it did not, and the last line proves it.
What does a function give back?
Whatever the return statement passes. A function with no return gives back undefined, implicitly, at the end of its body.
function noReturn() {
const value = 42;
}
function earlyExit(items) {
if (items.length === 0) {
return "empty";
}
return `has ${items.length} items`;
}
console.log(noReturn()); // undefined
console.log(typeof noReturn()); // "undefined"
console.log(earlyExit([])); // "empty"
console.log(earlyExit(["a", "b"])); // "has 2 items"
return also ends the function immediately. Everything after it in the same block never runs, which is what makes the early return pattern work: check the unhappy case, leave, and let the rest of the function deal with the normal one.
function withdraw(balance, amount) {
if (amount <= 0) {
return { ok: false, reason: "amount must be positive" };
}
if (amount > balance) {
return { ok: false, reason: "insufficient funds" };
}
return { ok: true, balance: balance - amount };
}
console.log(withdraw(500, 200)); // { ok: true, balance: 300 }
console.log(withdraw(500, 900)); // { ok: false, reason: 'insufficient funds' }
Each branch returns a complete answer, so the caller always gets a result and never has to work out what the function did not say. Returning an object like this beats returning a bare number, because it carries the success flag with it.
How do I return more than one value?
JavaScript functions return exactly one value. To return several, bundle them — an object if the values have names, an array if they are a sequence.
function splitAmount(total, people) {
const each = Math.floor(total / people);
const remainder = total - each * people;
return { each, remainder };
}
const result = splitAmount(100, 3);
console.log(result.each); // 33
console.log(result.remainder); // 1
An object is usually the better choice because the property names document themselves. result.each says what the number is; result[0] does not. When the values really are an ordered sequence and the order is obvious from the function name — a function called minAndMax returning [min, max] — an array is fine.
What is the difference between returning a value and changing something?
Some functions compute an answer and hand it back. Others change the world — they write to the page, push onto a list, send a request — and return nothing useful. Keeping the two apart is what makes code testable, because a function that only computes has no side effects to worry about.
// Pure: same input, same output, nothing outside changes.
function totalPaise(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Impure: it changes something outside itself.
function addToCart(cart, item) {
cart.push(item);
}
const cart = [];
addToCart(cart, { price: 250 });
console.log(cart.length); // 1
console.log(totalPaise(cart)); // 250
A pure function can be tested by calling it and checking the result, with no setup and nothing to clean up afterwards. An impure one has to be checked for what it changed. The best-designed functions are pure at the core, with one thin impure wrapper that does the changing.
The parameter and return rules worth remembering
- Arguments are matched by position. Missing ones become
undefined; extra ones are ignored. - Nothing throws when an argument is missing, so validate inside the function if the value matters.
- Parameters are local. Reassigning one never changes the caller’s variable.
- A function without a
returngives backundefined. returnends the function straight away, which is what makes early returns work.- Return one value. Bundle several into an object with named properties.
- Returning a result object —
{ ok, value, reason }— makes the success case explicit instead of leaving the caller to infer it.
Try it yourself: write a function applyCoupon(total, coupon) that takes a coupon object shaped like { type: "percent", value: 20 } or { type: "flat", value: 150 } and returns the final amount. Make it handle a missing coupon, an unknown coupon type and a negative value, each returning a clear result object rather than throwing. Then call it with each case and check that every path returns something sensible — that habit is what stops a discount function from quietly giving customers a free order.