Functions, Parameters and Closures

A function is a named, reusable block of behaviour: defined once, run many times, with different inputs. Functions are the unit of reuse and testing — and, through closures, the way JavaScript remembers state between calls. This is the most important lesson in Phase 1.

Why functions exist

Name a behaviour once, use it everywhere

// The behaviour: turn a raw name into a display name.
function displayName(first, last) {
  const clean = first.trim() + " " + last.trim();
  return clean.toUpperCase();
}

console.log(displayName("  ada ", "lovelace ")); // ADA LOVELACE
console.log(displayName("grace", "hopper"));     // GRACE HOPPER

The anatomy: function starts the declaration; displayName is the name; (first, last) are parameters — named placeholders for values that arrive later; the body runs on each call; return hands the result back and stops the function immediately.

The words parameter and argument

The parameter is the placeholder in the definition (first); the argument is the actual value in the call (" ada "). Mixing the words is harmless; mixing the order is not — arguments arrive in order.

Three ways to write one

Declaration, expression, arrow

// 1. Declaration — hoisted: callable even from lines above it.
function add(a, b) { return a + b; }

// 2. Function expression — a function stored like any value (NOT hoisted).
const multiply = function (a, b) { return a * b; };

// 3. Arrow — short; no braces means "return this automatically".
const square = n => n * n;

console.log(add(2, 3), multiply(2, 3), square(4)); // 5 6 16

Style: declarations for named top-level operations; arrows for the small functions you hand to addEventListener, map and friends. One arrow difference matters later: an arrow's this is inherited, not set (lesson 12).

Defaults and rest

function greet(name, greeting = "Hello") {
  return `${greeting}, ${name}!`;
}
console.log(greet("Ada"));       // Hello, Ada! — the default kicked in
console.log(greet("Ada", "Hi")); // Hi, Ada!

function total(first, ...others) { // ...others gathers the rest into an array
  return first + others.reduce((sum, n) => sum + n, 0);
}
console.log(total(1, 2, 3, 4));  // 10

Return semantics

return stops the function — and undefined is the default

function firstEven(numbers) {
  for (const n of numbers) {
    if (n % 2 === 0) return n;  // exits HERE, with the value
  }
  return "none";                // only reached when nothing matched
}

function noReturn() {
  const x = 1 + 1;              // computed, then dropped
}
console.log(noReturn());        // undefined — the lesson 01 mystery, solved

Every function returns something: the value of its return, or undefined. That is why the console echoed undefined after your first console.log line — console.log itself returns nothing.

Scope inside a function

Locals die with the call

const rate = 1.2; // outer

function convert(euro) {
  const result = euro * rate; // result is LOCAL: invisible outside
  return result;
}
console.log(result); // ReferenceError — locals vanish when the call ends

Each call creates a fresh scope: locals are born on entry and gone on return — which is why two calls never interfere, and the exact machinery that makes closures possible.

Closures

A function remembers its birthplace

Diagram: makeCounter's scope holds count and the returned function; the function keeps a live reference to count, so calls return 1, 2, 3

The returned function keeps its birth scope alive: count outlives the call that created it.

function makeCounter() {
  let count = 0;              // private: no code outside can reach it
  return function () {
    count += 1;               // the inner function REMEMBERS count
    return count;
  };
}

const next = makeCounter();
console.log(next(), next(), next()); // 1 2 3 — count survived between calls
console.log(count);                  // ReferenceError — it stays private

A function plus its captured scope is a closure. Each makeCounter() call creates a fresh, independent count — the pattern behind private state everywhere. Demo foundations/14_closures.html puts two independent counters on one page.

Pure functions and side effects

The testability line

// PURE: same input → same output; touches nothing outside itself.
function fullName(first, last) {
  return `${first} ${last}`;
}

// IMPURE: it changes the page — a side effect.
function showScore(score) {
  document.querySelector("#score").textContent = score;
}

Pure functions are trivial to test and reason about; side effects (DOM writes, network) are unavoidable but belong at the edges of a program. The professional shape: pure logic core, thin effect layer.

Callbacks: functions as values

"Run this later"

const button = document.querySelector("#save");

button.addEventListener("click", () => {
  console.log("clicked"); // stored by the browser, run on EVERY click
});

setTimeout(() => console.log("3 seconds passed"), 3000);

A function passed to another function, to be run later, is a callback. You have used them since lesson 02 without the name — and they are the doorway to asynchronous JavaScript (Phase 4) and the reason functions-as-values matter so much.

Practice: small helpers

The task

Run foundations/13_functions_lab.js with node, predicting each line. Then write formatPrice(cents) returning "€12.50" from 1250 (pure!), and preview foundations/14_closures.html — explain in one sentence why the two counters never interfere.

The checklist

  • You can name the three function forms and when each is the natural choice.
  • You can predict whether a call returns a value or undefined.
  • You can explain a closure as "a function plus its captured scope".
  • You can sort a function into pure vs impure — and say where the impure ones belong.