Arrow functions and closures
Why it matters
In JavaScript a function is a value, just like a number or a string. You can put it in a variable,
pass it to another function, return it from a function. A lot is built on this: click handlers, the array
methods map and filter (next topics), timers, server requests. And the short arrow syntax makes
such code compact and readable.
Function expressions
You can create a function and put it in a variable:
const greet = function (name) {
return `Hi, ${name}!`;
};
console.log(greet("Anna"));
Output:
Hi, Anna!
This is a function expression. Unlike the declaration function greet() {}, it can only be called
after the line where it's created.
Arrow functions
The same function, shorter:
const greet = (name) => {
return `Hi, ${name}!`;
};
And if the body is a single expression, the curly braces and return can be dropped: the result is returned automatically.
const greet = (name) => `Hi, ${name}!`;
const add = (a, b) => a + b;
const random = () => Math.random(); // no parameters — empty parentheses
const square = n => n * n; // one parameter — parentheses can be omitted
console.log(add(2, 3), square(5));
Output:
5 25
Careful with objects: to return an object in the short form, wrap it in parentheses —
() => ({ ok: true }). Otherwise the curly braces are taken as the function body.
Functions as arguments: callbacks
A function passed to another function so that it calls it is called a callback:
function repeat(times, action) {
for (let i = 1; i <= times; i++) {
action(i);
}
}
repeat(3, (n) => console.log("Repeat #" + n));
Output:
Repeat #1
Repeat #2
Repeat #3
The repeat function doesn't know what to do — it only knows how many times. What to do is decided by
the caller. That's how setTimeout(callback, ms), addEventListener("click", callback) and array methods work.
Functions that return functions
function multiplier(k) {
return (n) => n * k;
}
const triple = multiplier(3);
const tenTimes = multiplier(10);
console.log(triple(5), tenTimes(5));
Output:
15 50
multiplier is a function "factory": every call creates a new function with its own k.
Closures
Notice: triple is called after multiplier has already finished. How does it remember k = 3?
A function in JavaScript remembers the variables of the place where it was created. This is called a
closure.
function makeGreeter(greeting) {
let calls = 0;
return (name) => {
calls++;
return `${greeting}, ${name}! (call ${calls})`;
};
}
const hi = makeGreeter("Hello");
console.log(hi("Anna"));
console.log(hi("Timur"));
Output:
Hello, Anna! (call 1)
Hello, Timur! (call 2)
The variable calls lives as long as the function hi lives, and it's hidden: it can't be changed from
outside. That's how JS makes "private" data.
How it works inside
Every function call creates an environment — the set of its variables. Normally, after leaving the
function, the environment is thrown away. But if another function was created inside and returned
outside, it keeps a reference to the environment, and the environment stays alive. Each new call of
makeCounter() creates a new environment — that's why two counters don't interfere with each other.
Arrow functions have no this of their own and no prototype — so they're not used as constructors or
as object methods that need this (more on this in the objects topic).
Common mistakes
- Curly braces without
return.(n) => { n * 2 }returnsundefined. Either remove the braces or addreturn. - Calling instead of passing.
repeat(3, sayHi())passes the result of the call. You needrepeat(3, sayHi). - An object without parentheses.
() => { a: 1 }is a function body, not an object. You need() => ({ a: 1 }). - A shared variable outside the factory. If
let count = 0is written outsidemakeCounter, all counters will share one number. - Calling an expression before it's created.
const f = …can't be called above that line.
Cheat sheet
| What | How |
|---|---|
| Expression | const f = function (x) { return x; }; |
| Arrow with a body | const f = (x) => { return x * 2; }; |
| Short arrow | const f = (x) => x * 2; |
| No parameters | () => 42 |
| Return an object | () => ({ ok: true }) |
| Callback | repeat(3, (i) => console.log(i)) |
| Closure | a function remembers the variables of where it was created |