completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Closure patterns

Use closures to build IIFE modules, factory functions, once wrappers, memoized functions, and configurable function factories with private state.

By the end, you can
  • 01
    Hide data on purposeBuild an IIFE module and explain why outside code can't reach its variables.
  • 02
    Make separate copiesUse a factory function to make objects that each keep their own hidden data.
  • 03
    Wrap a functionWrite once, memoize, and small function factories that return functions with a setting built in.

Common ways to use closures

The Closures lesson showed one rule. A function can still use the variables from the place where it was made, even after that place has finished running. A function that does this is called a closure.

This lesson shows five common ways to use that rule in real code. Each way is called a pattern: a shape of code that programmers write again and again.

  • An IIFE module hides variables and shares only a few methods.
  • A factory function makes many objects, each with its own hidden data.
  • once lets a function run only one time.
  • memoize saves answers, so the same work is not done twice.
  • A function factory makes a new function with a setting built in.
The main idea

A closure pattern is a common way to use the variables a function remembers. You can use one to hide data, make separate copies, allow only one call, save answers, or build a function with a setting.

The code uses a few tools from earlier lessons:

  • function expressions and callbacks (a callback is a function you pass to another function),
  • arrow functions, like (n) => n * 2,
  • Map, which stores values under keys.

Later lessons go deeper. Memoization & laziness and Currying & partial application build on this one. The Modules stage explains why ES modules replaced most old IIFE modules.

Real-life analogyAn IIFE is a pop-up shop

A pop-up shop is set up on a street and opens right away. At closing time, the tables are packed away and the street is clear again. Only a small counter stays at the front.

In real life: The shop is set up in the morning
In JavaScript: The function is made
In real life: It opens for customers right away
In JavaScript: The () at the end calls it at once
In real life: At closing time, the tables are packed away
In JavaScript: Its variables don't become global variables
In real life: A small counter stays open at the front
In JavaScript: The returned object's methods can still be used

IIFEs and the module pattern

STEP THROUGH

IIFE stands for "immediately invoked function expression". In plain words: a function that is called the moment it is made. It looks like this: (function () { ... })();

  • The first pair of round brackets wraps the function. This lets JavaScript read it as a value, so it can be called right away.
  • The last pair, (), calls it.

Before ES modules existed, IIFEs were a common way to keep variables out of the global scope. The global scope is the place every script on the page shares. Any script can read or change a variable there, even by mistake. Variables made inside an IIFE stay inside it.

The module pattern adds one more step. The IIFE returns an object with methods. The methods can use the IIFE's variables, but outside code can't. In the code below, count is hidden. The returned increment() and get() methods are the only way to use it.

Step through an IIFE module
Step 0 of 8Ready
Your turn: follow the blue line

Step through an IIFE module. The setup runs once. Then the returned methods use count, which outside code can't reach.

Running in
  1. script
Next: line 1
  let count = 0;  console.log("IIFE ran once");  return {    increment() {      count += 1;      return count;    },    get() {      return count;    },  };})(); console.log(counterModule.count);console.log(counterModule.increment());console.log(counterModule.increment());console.log(counterModule.get());
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Three things to notice in the step-through:

  • The setup message printed once.
  • counterModule.count was undefined.
  • The methods still changed count, from 0 to 1 to 2.
An object called counterModule holds two methods, increment and get. One arrow goes from the object to a box called count, which holds 2. Both methods use this box. Reading counterModule.count gives undefined.increment()get()counterModule.count is undefinedboth use2counthidden inside the IIFE
The IIFE returned an object with two methods. Both methods use the count box, which holds 2 after the two increment() calls. The object has no count property, so counterModule.count is undefined.

New code usually uses ES modules instead. You will still see IIFE modules in older scripts and libraries. The Why modules? lesson explains that history later.

An ES module is a file that chooses what to share. It marks the things it shares with export, and other files get them with import. Names that are not exported stay inside the file. If the module later changes an exported variable, files that imported it see the new value.

The modern way: an ES moduleJavaScript
const settings = {
  theme: "light",
};

export function getTheme() {
  return settings.theme;
}

Line 1 makes settings. It is not exported, so it stays inside this file. Line 5 exports getTheme, so other files can import it by name. In a browser, the file must be loaded with type="module" on its script tag.

Factory functions

INTERACTIVE

A factory function is a normal function that makes and returns a new value, often an object. You don't need new or class. With closures, each call to the factory can also make its own hidden variables.

Real-life analogyA factory is a cookie cutter with recipe cards

One cookie cutter makes many cookies with the same shape. Under each cookie is its own small recipe card. You pick up the cookie, and the card stays tucked away underneath.

In real life: One cookie cutter
In JavaScript: One createAccount function
In real life: Many cookies
In JavaScript: Many returned account objects
In real life: A recipe card tucked under each cookie
In JavaScript: Each call’s own owner and balance variables
In real life: You pick up the cookie, not the card
In JavaScript: You call deposit, withdraw, and label
Make accounts with hidden balances
Try it yourself
Choose an account:

Two calls to the factory made two accounts. Pick one and change its balance.

Each call to the factory makes new owner and balance variables. The buttons call the real methods of the real account objects.

Lines 20 and 21 call createAccount twice. Each call makes its own owner and balance variables. Line 22 adds 10 to Ada's balance, and line 23 takes 5 from Grace's. Line 26 prints undefined, because the object has no balance property.

Left: createAccount Ada, 40 makes the object ada. An arrow goes from ada to its own balance box, which holds 50 after a deposit of 10. Right: createAccount Grace, 15 makes the object grace, with its own balance box, which holds 10 after a withdrawal of 5.createAccount("Ada", 40)ada50balanceAda: ₹50createAccount("Grace", 15)grace10balanceGrace: ₹10
Each createAccount() call makes a new balance box. Ada deposits 10, so her box goes from 40 to 50. Grace withdraws 5, so her box goes from 15 to 10. Changing one never changes the other.

The methods use the balance variable, not this.balance. (this is the object a method was called on.) That has a benefit: you can save a method in another variable, call it alone, and it still works. There is a cost too: each call to the factory makes new copies of all four methods. Classes can also hide data, with #private fields. A later Classes lesson covers them.

once and memoize

STEP + PLAY

A wrapper is a function that takes a function and returns a new function. The new function adds a small job around the original one, and it keeps what it needs to remember in a closure.

once(fn) is a wrapper that lets fn run only one time. The first call runs fn and saves its answer. Every later call skips fn and returns the saved answer. Use it for setup that must happen only once, like connecting to a server. Then a second call by mistake does no harm.

Step through once(fn)
Step 0 of 9Ready
Your turn: follow the blue line

Step through once(fn). The first call runs fn and saves its answer. Every later call returns that same answer.

Running in
  1. script
Next: line 13
function once(fn) {  let called = false;  let firstResult;  return function (...args) {    if (!called) {      called = true;      firstResult = fn(...args);    }    return firstResult;  };} const welcome = once((name) => {  runs += 1;  return "Welcome, " + name;});console.log(welcome("Ada"));console.log(welcome("Grace"));console.log(runs);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Left: the first call, welcome Ada, runs fn and saves Welcome, Ada in a box called firstResult. Right: the second call, welcome Grace, skips fn and returns the saved Welcome, Ada. runs is 1.welcome("Ada")1st call: fn runssaves"Welcome, Ada"firstResultwelcome("Grace")2nd call: fn skippedreturns itruns is 1
The first call, welcome("Ada"), runs fn and saves its answer in firstResult. The second call, welcome("Grace"), skips fn and returns the saved answer. Both print Welcome, Ada, and runs is 1.

memoize(fn) is a wrapper that saves answers. The first time it gets some inputs, it runs fn and saves the answer under a key made from those inputs. The next time the same inputs come in, it returns the saved answer and skips the work. Saved answers like these are called a cache. Here the cache is a Map kept in a closure. In the code below:

  • Line 2 makes the cache, an empty Map.
  • Line 4 turns the inputs into a text key with JSON.stringify. For example, the input 4 becomes the key "[4]".
  • Line 5 returns the saved answer if the key is already in the cache.
  • Lines 6 to 8 run fn, save its answer, and return it.

The experiment uses fib(n), which finds a Fibonacci number. In the Fibonacci numbers (0, 1, 1, 2, 3, 5, 8, ...), each number is the sum of the two before it. The plain version calls itself for the same smaller numbers again and again. The experiment counts the calls of both versions.

Count the calls with and without memoize
A simple memoize utilityPop out in the code editor (opens in a new tab)JavaScript
function memoize(fn) {  const cache = new Map();  return function (...args) {    const key = JSON.stringify(args);    if (cache.has(key)) return cache.get(key);    const result = fn(...args);    cache.set(key, result);    return result;  };}
Call countsfib(7)
Plain version41 calls
Memoized version13 calls
Answer13
Try it yourself
Pick n for fib(n)

Both versions give 13. The plain version made 41 calls. The memoized version made 13 calls, because it returns saved answers instead of working them out again.

This counts calls instead of timing them. Counting the real calls shows clearly how much work the cache saves.
Cache keys and memory

JSON.stringify(args) is fine for small examples, but it is not a perfect key for every input. For example, { a: 1, b: 2 } and { b: 2, a: 1 } give different keys. A function becomes null in the key, so two different functions get the same key. A cache also grows with every new input unless you limit it or clear it. The next lesson, Closures in loops, timers & memory, comes back to the memory that closures keep.

Function factories

INTERACTIVE

A function factory is a function that returns a new function with a setting built in. You give the setting once, in the outer call. The returned function remembers it through a closure. The code below has three function factories:

  • makeMultiplier(3) returns a function that multiplies by 3.
  • makeGreeting("Hello") returns a function that says Hello to a name.
  • makeValidator({ min: 5, max: 12 }) returns a function that checks if a number is from 5 to 12.
Build new functions from settings
Three function factoriesPop out in the code editor (opens in a new tab)JavaScript
function makeMultiplier(factor) {  return function multiply(number) {    return number * factor;  };} function makeGreeting(greeting) {  return function greet(name) {    return greeting + ", " + name + "!";  };} function makeValidator({ min, max }) {  return function isInRange(value) {    return value >= min && value <= max;  };} const triple = makeMultiplier(3);const hello = makeGreeting("Hello");const isChildAge = makeValidator({ min: 5, max: 12 });console.log(triple(7));console.log(hello("Ada"));console.log(isChildAge(9));
The new functionslive output
multiplier(7)21
greeter("Ada")Hello, Ada!
validator(9)true
Try it yourself

The returned functions remember their settings: the factor 3, the greeting "Hello", and the range 5 to 12.

Each button changes a setting in an outer call. The factory builds a new function, and the output below uses it.
The function triple, made by makeMultiplier 3, has an arrow to a box called factor, which holds 3. triple of 7 gives 21.from makeMultiplier(3)triple()triple(7) gives21remembers3factorkept from the outer call
makeMultiplier(3) made the box factor with 3 in it, then returned triple. Every call to triple reads that box, so triple(7) gives 21.

Line 19 calls makeMultiplier(3) and saves the returned function as triple. Line 22 calls triple(7) and prints 21. Lines 23 and 24 print Hello, Ada! and true, because 9 is from 5 to 12.

Later, the Currying & partial application lesson changes the shape of functions in a stricter way. For now, remember one habit: put the setting in the outer call, and put the input that changes in the call to the returned function.

Which closure pattern is this?
  • (function () { setup(); })();
  • Return an object whose methods share a hidden count.
  • createAccount('Ada', 40) returns a new account object.
  • Run connect() the first time, and return the same answer later.
  • Save the answer to fib(35), so the next fib(35) returns at once.
  • makeMultiplier(3) returns a new triple function.
Try it yourself
0 of 6 correct

Sort each card into the pattern it matches best. Real code often mixes patterns, so pick the closest one.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you’ll use this

Use a closure pattern when you want a few functions that remember something, but you don't want other code to touch that memory directly. Some examples:

  • a part of a page that keeps a counter,
  • a form helper that keeps its checking rules,
  • a data helper that remembers recent answers.

These patterns are plain JavaScript. They work anywhere JavaScript runs, not only in the browser.

Partial application means giving a function one argument now and the rest later. It is useful when a callback needs the same setting every time, like a tax rate or a user ID. It is related to currying, which turns a function into a chain of functions that each take one argument. Partial application is simpler: fix some arguments now, and pass the rest later.

Fix one argument for laterPop out in the code editor (opens in a new tab)JavaScript
function partial(fn, firstArg) {  return function (secondArg) {    return fn(firstArg, secondArg);  };} function addTax(rate, amount) {  return amount + amount * rate;} const addTenPercent = partial(addTax, 0.10);console.log(addTenPercent(50));
  • Line 1 makes partial. It returns a new function that remembers firstArg.
  • Line 7 makes addTax, which needs a rate and an amount.
  • Line 11 gives partial the rate 0.10 now.
  • Line 12 gives only the amount, 50, and prints 55: 50 plus 10% of 50.
Debounce: wait until the calls stop

Debouncing means waiting until calls stop coming for a set time, then doing the work once. A search box gets a call for every letter you type. With debouncing, it sends one request with the final text, not one request per letter.

Only the last call runsJavaScript
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

const saveSearch = debounce((query) => console.log("saved: " + query), 10);
saveSearch("t");
saveSearch("te");
saveSearch("tea");

setTimeout runs a function after a delay, and clearTimeout cancels one that hasn't run yet. Each call to saveSearch cancels the old timer, then starts a new one. The three calls pass "t", "te", and "tea". Only the last timer gets to run, so the code prints saved: tea.

A real search box usually needs one more method, cancel, which stops a waiting timer. Call it when the search box is removed from the page. Otherwise a late timer can still run and try to update a part of the page that is gone.

Debounce is different from throttle. Debounce waits for a quiet gap. Throttle lets the work run at most once in each set time, even while calls keep coming.

Let go of data the closure keepsPop out in the code editor (opens in a new tab)JavaScript
function makePreview() {  let rows = ["order 1", "order 2", "order 3"];  return {    size() {      return rows.length;    },    clear() {      rows = [];      return rows.length;    },  };} const preview = makePreview();console.log(preview.size());console.log(preview.clear());

A closure keeps alive every value it can still reach, even a large array. That is useful while the page needs the data. But if the closure lives a long time, for example inside an event listener, it keeps the data long after you stopped needing it.

Line 2 makes rows, an array that both methods use. Line 8, inside clear(), puts an empty array in its place, so JavaScript can free the memory the old array used. The code prints 3, then 0. In a real app, also remove event listeners and drop your saved copy of the returned object when that part of the page goes away.

Three small tools that rememberPop out in the code editor (opens in a new tab)JavaScript
function memoize(fn) {  const cache = new Map();  return function (value) {    if (cache.has(value)) return cache.get(value);    const result = fn(value);    cache.set(value, result);    return result;  };} function makeIdGenerator(prefix) {  let next = 1;  return function id() {    const value = prefix + next;    next += 1;    return value;  };} function createToggle(initial = false) {  let on = initial;  return {    flip() {      on = !on;      return on;    },    value() {      return on;    },  };} const square = memoize((n) => n * n);console.log(square(4));const nextTodoId = makeIdGenerator("todo-");console.log(nextTodoId());console.log(nextTodoId());const toggle = createToggle();console.log(toggle.flip());console.log(toggle.value());

Each function at the top makes a small tool that remembers something:

  • Line 1, memoize, remembers answers. This short version takes one input and uses it as the key.
  • Line 11, makeIdGenerator, remembers the next number to use.
  • Line 20, createToggle, remembers whether it is on or off.

Line 34 prints 16, the square of 4. Lines 36 and 37 print todo-1 and todo-2, because next goes up by 1 after each call. Line 39 flips the toggle from false to true and prints true. Line 40 reads it and prints true again. In each case, the caller gets a simple function or object and never handles the remembered variable itself.

Factories or classes?

Factories and classes can both make many similar objects. Pick the one that makes your code clearest to you and your team.

Here is one difference you can test. Save a method in a new variable and call it alone. The factory method ada.getBalance still works, because it reads its balance variable directly. A class method that reads this.balance loses its object when it is called alone. To fix that, call it as ada.getBalance(), or save ada.getBalance.bind(ada), which fixes this to ada.

The table below compares the two. Read each row as a question, then compare the two answers.

Factory functions compared with classes
What you want to knowWith a factoryWith a class
How is data kept hidden?Hidden variables live in a closure. No property can reach them.#private fields are hidden by the language itself.
Do the methods need this?No. The methods read the closure's variables directly.Usually yes. A method saved and called alone loses its object.
How many copies of each method are there?Each object gets its own new copy of every method.All objects share one copy of each method.
Does instanceof work?Usually no. The object doesn't record which factory made it.Yes. obj instanceof ClassName gives true.
When is it a good fit?Small tools, wrappers, settings, and hidden data without subclasses.Many objects that share methods, subclasses, or a team that prefers classes.

Common misconceptions

  • “An IIFE runs later, like a timer.” It is a normal function call. It runs right away, and the next line waits until it finishes.
  • “Hidden means safe from all code.” Other code can't read a hidden variable directly. But the methods you return still decide what other code can do with it.
  • “All objects from one factory share the same data.” They share the same code. Each call makes new variables, unless the factory uses a variable from outside itself on purpose.
  • “Memoize always makes code faster.” It helps when the same slow call repeats. It also costs time to build keys and look them up, and memory to store the answers.
  • “Every function factory is currying.” They are related. Currying is one exact shape: a chain of functions that each take one argument. The Currying & partial application lesson covers it.
  • “Closures always waste memory.” A closure keeps the values it can still reach. That is often what you want. Only closures that live a long time and hold large data need care.

Practice closure patterns

5 EXERCISES
Exercise 1 · Warm-upWrite once(fn)

Write the wrapper so the setup function runs only once. Then predict the three printed lines.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function once(fn) {
  let called = false;
  let firstResult;
  return function (...args) {
    if (!called) {
      called = true;
      firstResult = fn(...args);
    }
    return firstResult;
  };
}
let calls = 0;
const init = once(() => {
  calls += 1;
  return "ready";
});
console.log(init());
console.log(init());
console.log(calls);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeMemoize and count the calls

    Run the program and check the last printed line: the call count. Counting calls shows exactly what the cache saved.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function memoize(fn) {
      const cache = new Map();
      return function (...args) {
        const key = JSON.stringify(args);
        if (cache.has(key)) return cache.get(key);
        const result = fn(...args);
        cache.set(key, result);
        return result;
      };
    }
    let calls = 0;
    const double = memoize((n) => {
      calls += 1;
      return n * 2;
    });
    console.log(double(4));
    console.log(double(4));
    console.log(double(5));
    console.log(calls);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeBuild a counter with step and reset

      Write makeCounter(step). Each call to next() should go up by step, and reset() should go back to zero.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function makeCounter(step) {
        let count = 0;
        return {
          next() {
            count += step;
            return count;
          },
          reset() {
            count = 0;
            return count;
          },
        };
      }
      const byThree = makeCounter(3);
      console.log(byThree.next());
      console.log(byThree.next());
      console.log(byThree.reset());

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeTurn a global script into an IIFE module

        A script has let count = 0 at its top level, so count is global. Other scripts on the page change count by mistake. Which variable should you hide inside an IIFE?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeMake a tax-rate function factory

          Write makeTax(rate). Each function it returns should add its own tax rate to an amount.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function makeTax(rate) {
            return function addTax(amount) {
              return amount + amount * rate;
            };
          }
          const addTenPercent = makeTax(0.10);
          console.log(addTenPercent(50));

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            Closure patterns quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is the main reason to wrap an old script's setup code in an IIFE?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this module-pattern code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const module = (function () {
                let count = 0;
                return { inc() { count += 1; return count; } };
              })();
              console.log(module.count);
              console.log(module.inc());

              Choose an answer to see the explanation.

            3. Question 3 of 7What does each call to a factory function usually make?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this once-wrapper code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function once(fn) {
                let called = false;
                let firstResult;
                return (...args) => {
                  if (!called) {
                    called = true;
                    firstResult = fn(...args);
                  }
                  return firstResult;
                };
              }
              let calls = 0;
              const greet = once((name) => { calls += 1; return "Hi " + name; });
              console.log(greet("Ada"));
              console.log(greet("Grace"));
              console.log(calls);

              Choose an answer to see the explanation.

            5. Question 5 of 7What is a common problem with a simple memoize that uses JSON.stringify(args) as the key?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does this function-factory code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function makeMultiplier(factor) {
                return (number) => number * factor;
              }
              const double = makeMultiplier(2);
              const triple = makeMultiplier(3);
              console.log(double(5));
              console.log(triple(5));

              Choose an answer to see the explanation.

            7. Question 7 of 7Which sentence about objects made by a factory is true?

              Choose an answer to see the explanation.

            Key takeaways

            • An IIFE runs as soon as it is made. In the module pattern, it returns methods that use its hidden variables.
            • Each call to a factory function makes a new object with its own hidden data.
            • once remembers whether it has run. memoize remembers answers it already worked out.
            • A function factory takes a setting in the outer call and returns a function that remembers it.
            • A closure keeps alive the values it uses, so large data in a long-lived closure costs memory.

            Closure patterns are common ways to use functions that remember hidden variables, to build small, safe tools.

            Up next: Closures in loops, timers & memory.

            CompleteFrontend Clear concepts. Working examples.