Loading...

JavaScript Closures

You already know scope: which names are visible from here. One step further: an inner function can keep using an outer name even after the outer function has finished. That lasting link is a closure.

What a closure means

A closure forms when a function is created inside another function and still reaches the outer function’s bindings later. The outer call may be over; the inner function still “remembers” those names. You do not write the word closure in code — you write nested functions, and JavaScript keeps the needed outer bindings alive.

Three pieces to watch

📊 Outer function, binding, and inner function

Piece Role Beginner cue
outer function runs once (or more) and often returns an inner function the “setup” call
outer binding a parameter or let / const in the outer function the value the inner still needs
inner function uses that binding when you call it later the function you keep

If you can point to those three, you can explain almost every beginner closure example. The surprise is not the nesting — it is that the outer binding stays useful after the outer call returns.

Inner function reads an outer name

javascript
function makeLine(title) {
function show() {
console.log(title);
}
show();
}
makeLine("Shop");

During the call, the inner looks outward

show has no local title. Lookup walks out to makeLine and finds the parameter. That is ordinary nested scope. Closures go one step further: keep show (or a twin) for later, after makeLine has returned.

Keep the inner function after return

javascript
function makeLine(title) {
return function show() {
console.log(title);
};
}
let printShop = makeLine("Shop");
printShop();

The outer call ends — the memory stays

makeLine("Shop") returns a function. When you call printShop later, it still prints Shop. The parameter title from that finished call is still reachable. That lasting capture is the closure.

A small greeting factory

javascript
function makeGreeter(name) {
return function () {
return "Hello, " + name;
};
}
let hiSara = makeGreeter("Sara");
console.log(hiSara());

Factories return a ready function

makeGreeter is a tiny factory: one outer call, one customized function back. hiSara always greets Sara because it closed over that name. Change the argument to makeGreeter, get a different greeter — same pattern, new memory.

A counter that remembers

javascript
function makeCounter() {
let n = 0;
return function () {
n = n + 1;
return n;
};
}
let tick = makeCounter();
console.log(tick());
console.log(tick());

Private-ish state with a counter

n lives in the outer function. Outside code cannot touch n directly — only the returned function can. First call returns 1, second returns 2. The counter “remembers” without a global variable.

Two counters, two memories

javascript
function makeCounter() {
let n = 0;
return function () {
n = n + 1;
return n;
};
}
let a = makeCounter();
let b = makeCounter();
console.log(a());
console.log(a());
console.log(b());

Each outer call gets its own memory

a and b each came from a separate makeCounter() call, so each has its own n. Two bumps on a leave b still at its first step. Closures do not automatically share one bag of state unless you intentionally share one outer binding.

Show a closed-over value on the page

Build a greeter with a factory, call it once, and put the returned string into a paragraph.

Page demo with a closure

html
<!DOCTYPE html>
<html>
<head>
<title>closures — page</title>
</head>
<body>
<p id="msg">…</p>
<script>
function makeGreeter(name) {
return function () {
return "Hello, " + name;
};
}
let hi = makeGreeter("Omar");
let box = document.getElementById("msg");
box.textContent = hi();
</script>
</body>
</html>

The paragraph shows Hello, Omar. The outer call finished; hi still knows name. Change only the string passed to makeGreeter, refresh, and read the new line. Next lesson covers objects — grouping related values under one name.

Practice steps

  1. Write a function that returns an inner function which logs an outer parameter.
  2. Store that returned function in a variable and call it after the outer call has finished.
  3. Build makeGreeter("Lina") and log the string it returns.
  4. Create one counter; call it twice and confirm you see 1 then 2.
  5. Create two counters; bump only the first twice and check the second still starts at 1.
  6. In the HTML demo, change only the name argument, refresh, and read the paragraph.

Frequent slip-ups

  • Assuming outer locals vanish the instant the outer function returns — the inner can still see them
  • Expecting every call to makeCounter() to share one n — each outer call usually gets its own
  • Thinking “closure” means only the return keyword — return can hand back the inner function, but the memory is the closed-over binding
  • Putting every remembered value on the global object “to keep it simple”
  • Expecting a full objects lesson here — grouping properties under one value is the next page

Summary

  • A closure lets an inner function keep using outer names after the outer call ends
  • You write nested functions; JavaScript keeps the needed bindings alive
  • Factories return a customized function that remembers its setup values
  • Counters hide a private n behind the returned function
  • Separate outer calls usually mean separate memories
  • Page updates can show a string produced by a closed-over name
  • Next: objects — related values under one name

🧠 Test Your Knowledge

Ready to Start

Test Your Knowledge

Challenge yourself with this interactive quiz and see how well you understand the topic

❓
8
Questions
🎯
70%
To Pass
♾️
∞
Time
🔄
∞
Attempts

📝 Instructions

  • Read each question carefully
  • Select the best answer for each question
  • You can retake the quiz as many times as you want
  • Your progress will be shown at the top