JavaScript•25 min read•Beginner
Functions, Arrows & Closures
Function declarations, expressions, arrow syntax, default parameters, and the closure model that powers React.
Three ways to define a function
javascript
// 1. Function declaration — hoisted, has a name
function add(a, b) {
return a + b;
}
// 2. Function expression — assigned to a variable, NOT hoisted
const sub = function (a, b) {
return a - b;
};
// 3. Arrow function — concise, no own `this`
const mul = (a, b) => a * b;
console.log(add(2, 3), sub(2, 3), mul(2, 3));All three are functions. The differences are subtle but matter:
- Declarations are hoisted — you can call them before they appear in the file.
- Expressions and arrows are NOT hoisted — they exist only after the line that defines them.
- Arrow functions don't have their own `this`. They inherit it from the surrounding scope. (This is the #1 reason arrows exist.)
Arrow function syntax shortcuts
javascript
// Single parameter — parens optional
const double = n => n * 2;
// Single expression body — no braces, implicit return
const square = n => n * n;
// Multiple statements — braces and explicit return
const describe = n => {
const kind = n >= 0 ? "non-negative" : "negative";
return `${n} is ${kind}`;
};
// Returning an object literal — wrap in parens to disambiguate from a block
const point = (x, y) => ({ x, y });Default and rest parameters
javascript
function greet(name = "world") {
return `Hello, ${name}!`;
}
console.log(greet()); // Hello, world!
console.log(greet("Ada")); // Hello, Ada!
// Rest collects extra arguments into an array
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10Closures
A closure is a function that "remembers" the variables from the scope where it was defined, even after that scope has finished executing. Closures are not a quirk — they're the foundation of how nearly every modern JS pattern works (modules, callbacks, React hooks).
🎒
Real-life analogy — A function with a backpack
When you create a function inside another function, it packs the outer function's variables into a backpack and carries it forever. Even after the outer function returns and 'leaves the building', the inner function still has access to everything in its backpack.
Closure: a function carrying its scope around
When an inner function references variables from its outer scope, those variables stay alive — even after the outer function returns.
Global
no variables
Output
Call stack
empty
Empty global scope.
javascript
function makeCounter() {
let count = 0; // local to makeCounter
return function () {
count += 1; // but the inner function still sees it
return count;
};
}
const tick = makeCounter();
console.log(tick(), tick(), tick()); // 1, 2, 3
const tick2 = makeCounter();
console.log(tick2()); // 1 — fresh, independent countWhy does this work? When `makeCounter` returns, its scope normally would be garbage-collected. But the inner function captured a reference to `count`, so the JS engine keeps that variable alive as long as the inner function exists. Each call to `makeCounter` creates a new private `count`.
Practical use: data privacy
javascript
function makeWallet(initial) {
let balance = initial;
return {
deposit(amount) { balance += amount; },
withdraw(amount) {
if (amount > balance) throw new Error("insufficient");
balance -= amount;
},
getBalance() { return balance; },
};
}
const w = makeWallet(100);
w.deposit(50);
console.log(w.getBalance()); // 150
// w.balance is undefined — there\u2019s no way to touch it from outside💡 Tip
If you understand closures, you understand 80% of "why does my React component see stale state?" debugging. The closure captured the old value.