A function names a reusable operation. Its parameters receive arguments, and return gives a result to the caller. Reaching the end without a return produces undefined. Keep calculations separate from printing or updating a screen: a function that returns a total can be tested without intercepting the console.
Lexical scope means that access to surrounding bindings depends on where a function was written. A closure is a function together with access to those surrounding bindings. It does not take a frozen photograph of every value. If a captured binding changes, a later call can observe that change.
Build independent state
function makeCounter(start = 0) {
let count = start;
return () => {
count += 1;
return count;
};
}
const first = makeCounter();
const second = makeCounter(10);
console.log(first(), second(), first());
function sum(...values) {
return values.reduce((total, value) => total + value, 0);
}
const durations = [20, 30];
console.log(sum(...durations), sum());
The first line is 1 11 2: each call to makeCounter created a different count binding. The second is 50 0. A rest parameter gathers arguments into an array, while spreading an array supplies individual arguments. Similar punctuation can therefore perform opposite jobs depending on its position.
Default parameters apply when an argument is missing or undefined, not when it is null. Returning an object from a concise arrow function needs parentheses, such as value => ({ value }); braces alone would start a function body. Ordinary functions have their own call-dependent this; arrows do not. The object chapter develops that distinction.
Callbacks and scope mistakes
A callback is just a function passed to another operation. In items.map(formatItem), pass the function; writing formatItem() calls it immediately and passes its result instead. Read the receiving API's callback arguments. Passing parseInt directly to map, for example, also passes the item's index as the radix argument, producing surprising results. Write an explicit wrapper when the signatures differ.
Prefer block-scoped loop variables. A closure created in each let iteration can retain that iteration's binding, while several closures over a single outer mutable binding share it. Also remember that a captured array or object can remain reachable for as long as the closure remains reachable. Closing over a large cache has memory consequences even though garbage collection is automatic.
Recursion means calling a function from itself. It needs a stopping condition and a smaller problem at each step. Deep recursion can exceed the call stack; an ordinary loop is often a safer starting point for a long sequence. Use function composition to express a pipeline, not to hide simple logic behind layers of wrappers.
Exercise: create makeBudget(limit) returning a function that accepts positive session durations and reports the remaining minutes. Two budgets must not affect each other. Reject an invalid duration without changing the remaining total.
Solution guidance: validate before updating the captured remaining value. Define what happens if a session exceeds the remainder; for example, reject it instead of allowing a negative result. Start with 60, accept 25 and 15, and expect 35 then 20. Test a second budget between those calls to expose accidental shared state.
Reference: Functions and closures.
Previous: Values, Conversion and Control Flow. Next: Arrays, Objects, Maps and Sets.

