Learn by reading through in order

Closures — Functions That Remember Outer Variables

Closures are functions that remember variables from an outer scope. Covers counters that persist across calls, function factories, and functions made in loops.

Sometimes you want a function to hold a value that grows each time you call it, like a page's view count. If you count with an ordinary variable, though, either the value doesn't stick around, or any line can overwrite it.

This article covers closures, functions that remember variables from an outer scope, and function factories, functions that return a function with a setting built in.

Keeping a Value Between Calls — Closure Basics

Say you want to count downloads of a document. If you declare a let inside the counting function, it goes back to 0 on every call. If you declare it at the outermost level, the value sticks around, but now it sits where any line can overwrite it.

With a closure (a function that remembers variables from an outer scope), you can keep a value between calls while stopping lines outside from overwriting it. If you declare a variable inside a function and return a function that reads and writes it, the returned function takes that variable with it.

function makeDownloadCounter() {
  let total = 0;                  // A variable that exists only inside makeDownloadCounter

  const countUp = () => {
    total = total + 1;            // Updates the outer total
    return total;
  };

  return countUp;                 // Returns the function itself
}

const countDownload = makeDownloadCounter();   // Makes one counter
console.log(countDownload());     // 1
console.log(countDownload());     // 2
console.log(countDownload());     // 3 (picks up from the previous value)
// console.log(total);            // ReferenceError: total is not defined
The Variable a Returned Function Remembers
Outermost level (not inside any braces)
  • const countDownload = makeDownloadCounter() — holds the returned function
  • console.log(total) — the name isn't visible, so ReferenceError
makeDownloadCounter's braces
  • let total = 0 — calling countDownload() doesn't reset it to 0
  • return countUp — returns the function itself, not a value
countUp's braces
  • total = total + 1 — updates the outer total
  • Can still read and write this total after the outer function has finished running
countUp is returned to the outermost level and still remembers the outer total. Even after the function finishes, the remembered variable stays around.

The function named countUp on the inside is called as countDownload on the outside. What return hands over is the function itself, so you can give the receiving variable whatever name fits where you use it. The table below compares where you put the counted value, whether it survives between calls, and whether it can be overwritten.

Where the Count LivesValue Between CallsCan Outside Lines Overwrite It?
Declare let inside the counting functionGone when the call ends; starts from 0 next timeNo, the name isn't visible
Declare let at the outermost levelKeptYes, from any line
Declare let in an outer function and return an inner functionContinues from the previous value on each call to the returned functionChanges only through the returned function

A free video trial allows up to three plays. Declare the variables yourself.

① Write makeTrialCounter, which returns a function that starts with 3 plays left, subtracts 1 on each call, and returns the number left. Don't go below 0.

② Create a trial function with makeTrialCounter.

③ Call the function from ② four times, and display each result in the form "Plays left: N".

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

Making Functions with a Setting Built In — Function Factories

Say the point rate differs by membership rank. If you pass the rate as an argument on every calculation, callers can mix up the ranks, and you end up writing the same rate over and over. Here, you'd rather set the rate once, ahead of time.

With a function factory (a function that takes a setting and returns a function that remembers it), you only pass the rate once. A closure remembers arguments too, so each returned function keeps reading the rate it was created with.

function makePointCalculator(rate) {
  // rate is an argument. The returned function keeps remembering it
  return (amount) => Math.floor(amount * rate);
}

const regularPoint = makePointCalculator(0.01);    // Regular members get 1%
const goldPoint = makePointCalculator(0.05);       // Gold members get 5%
const platinumPoint = makePointCalculator(0.1);    // Platinum members get 10%

console.log(regularPoint(3000));    // 30
console.log(goldPoint(3000));       // 150
console.log(platinumPoint(3000));   // 300

// Skip the variable and calculate on the spot with two pairs of parentheses
console.log(makePointCalculator(0.05)(5000));   // 250
Three Functions from One Definition
One definition:makePointCalculatorPass (0.01)→ regularPointPass (0.05)→ goldPointPass (0.1)→ platinumPointRemembered rateis 0.01Remembered rateis 0.05Remembered rateis 0.13000 yenreturns 303000 yenreturns 1503000 yenreturns 300
Every function body is the same expression; the only difference is the rate each one remembers. Each function keeps reading the rate you passed in, unless something reassigns it.

In makePointCalculator(0.05)(5000), the first pair of parentheses creates a function that remembers the rate, and the second pair passes in the amount and calculates on the spot. With only one pair of parentheses, what comes back is a function, not a calculated number.

The main store and the branch have different free-shipping thresholds and shipping fees. cartTotals is already declared.

① Write makeShippingFee, which takes the free-shipping threshold and the fee, and returns a function that takes a cart total and returns the shipping fee.

② Create functions for the main store (free at 4000 yen or more, otherwise 500 yen) and the branch (free at 3000 yen or more, otherwise 350 yen).

③ Run each amount in cartTotals through both, and display "N yen → Main store N yen / Branch N yen".

JavaScript / TypeScript Editor

Run code to see output

A New Variable Every Time — Factories and let

Say you want to count down the remaining stock for each product. If you set up just one shared variable for the subtraction, reserving shirts also eats into the remaining caps. Each product needs its own value.

As with makeDownloadCounter from the start of this article, if you declare let inside a function that returns a function, that variable is created fresh every time you call the outer function. The returned functions don't share variables, so even though they come from the same definition, each one reads and writes only its own.

function makeStockReserver(stock) {
  let rest = stock;               // Created fresh on each call

  return (count) => {
    rest = rest - count;
    return rest;
  };
}

const reserveShirt = makeStockReserver(10);
const reserveCap = makeStockReserver(4);

console.log(reserveShirt(3));     // 7
console.log(reserveCap(1));       // 3
console.log(reserveShirt(2));     // 5 (only the shirt stock goes down)
console.log(reserveCap(1));       // 2
A New Variable on Every Call
makeStockReserver(10) runsCreates a variablerest = 10makeStockReserver(4) runsCreates another,separate rest = 4Call reserveShirt(3)and reserveCap(1)Returns 7 and 3independently
Even from the same definition, each function gets its own rest. Lowering one doesn't touch the other's remaining stock.

reserveShirt(2) in the third console.log lowers only the shirt stock, from 7 to 5, and the cap side stays at 3. The next reserveCap(1) returns 2, which shows that the cap function keeps its own count as well.

Calling the Factory Every Time Starts Over

If you write makeStockReserver(10)(3) twice, both return 7. That's because each call creates a separate rest starting from 10. To carry the reduced stock over to the next reservation, put the returned function in a variable like reserveShirt, and call that variable.

Issue sequential IDs for orders and returns, each counting independently. Declare the variables yourself.

① Write makeIdIssuer, which takes a prefix and returns a function that returns IDs in sequence — "prefix-1", "prefix-2", and so on — on each call.

② Create one issuing function for ORD and one for RTN.

③ Issue two order IDs and one return ID, displaying each.

④ Issue one more order ID, display it, and check that the numbering picks up where it left off.

JavaScript / TypeScript Editor

Run code to see output

Creating Functions in a Loop — A Separate let for Each Iteration

Say you want to set up a batch of output functions, one per log level. If you create functions inside a loop and store them in an array, you call them after the loop has finished. Which iteration's value each function remembers then depends on how you declared the loop variable.

This course doesn't use var, but putting it next to let makes the difference clear. With let in the for parentheses, a new variable is created for each iteration, and a function created in that iteration remembers that variable. var shares a single variable across all iterations, so every function reads the value left after the loop.

const levels = ["info", "warn", "error"];

// var shares one variable across every iteration
const varLoggers = [];
for (var i = 0; i < levels.length; i++) {
  varLoggers.push(() => `[${levels[i]}]`);
}
console.log(varLoggers[0]());     // [undefined]

// let creates a new variable for each iteration
const letLoggers = [];
for (let j = 0; j < levels.length; j++) {
  letLoggers.push(() => `[${levels[j]}]`);
}
console.log(letLoggers[0]());     // [info]
console.log(letLoggers[2]());     // [error]
What Functions That Remember a Loop Variable End Up Reading
var in the forparenthesesOne variableacross iterationsAll 3 functionsread the same iReturns[undefined]let in the forparenthesesA new variableeach iterationEach remembers0, 1, or 2Returns[info]
On the var side, all three functions read the same variable; on the let side, each reads its own iteration's value. What a function remembers depends on whether a new variable is created each iteration.

The var side gives [undefined] because i is 3 once the loop exits, and levels[3] doesn't exist. On the let side, levels[j] is also read at call time, but the j it reads is the variable created for that iteration, so each iteration's value is still there.

A Closure Doesn't Remember the Value at Creation Time

What a closure remembers is the variable itself, and it reads whatever value the variable holds when called. If you create a function that reads let rate = 0.05; and then reassign rate = 0.1;, that function calculates with 0.1 too. For a setting you want fixed, pass it as a factory argument, as in makePointCalculator(0.05).

Create a function for each payment method that displays a confirmation notice. methods, labels, and notifiers are already declared.

① Loop over methods with let in the for parentheses.

② In each iteration, use the payment method's name as the key, and put a function into notifiers that takes an order number and displays a notice like "[Bank transfer] Received order A-1002".

③ After the loop, pass A-1002 to bank and A-1003 to cod.

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1If you call countDownload() twice and then display total at the outermost level, what happens?

Q2reserveCap starts with 4 in stock. After reserveShirt takes away 3, you pass 1 to reserveCap. What does it return?

Q3If you call a function created inside for (var i = 0; ...) after the loop has finished, what is i?