DMarlon Studio

Lesson 1 · 1 of 20

Scope and closures · JavaScript

Learn what variable scope means in JavaScript and how a closure keeps private data between calls, using order and invoice counters.

JavaScript · Scope and closures in DMarlon Studio: the example's code and its output in the terminal

Every function creates its own scope: variables declared inside it only exist there. In the example, count lives inside createCounter, and from the outside typeof count is undefined: the rest of the program cannot see it.

The interesting part is that createCounter returns another function, next, which keeps using count even after createCounter has finished. That is a closure: the inner function holds on to the scope it was born in. Each call to createCounter creates a fresh scope, which is why nextOrder and nextInvoice keep separate counts (Order #3 versus Invoice #1).

let and const are block-scoped: in a for loop, let i creates a new variable on every turn. Each arrow function stored in tasks remembers its own i, so you get task 1, task 2, task 3. With the old var, all three would share one variable and print task 4 three times.

Closures are also a way to build pre-configured functions: makeDiscount(0.3) returns a function that remembers the 30% discount and applies it to every price it receives. This is the foundation of patterns you will meet later in the course, such as memoization and the module pattern.

Code · main.js

// Each call to createCounter gets its own private "count".function createCounter(label) {  let count = 0; // lives on inside the closure  return function next() {    count += 1;    return `${label} #${count}`;  };}const nextOrder = createCounter("Order");const nextInvoice = createCounter("Invoice");console.log(nextOrder());console.log(nextOrder());console.log(nextInvoice());console.log(nextOrder());// Block scope: let creates a new binding on every loop turn.const tasks = [];for (let i = 1; i <= 3; i++) {  tasks.push(() => `task ${i}`);}console.log(tasks.map((run) => run()).join(", "));// A closure can also keep settings, such as a discount rate.function makeDiscount(rate) {  return (price) => +(price * (1 - rate)).toFixed(2);}const blackFriday = makeDiscount(0.3);console.log("Black Friday price:", blackFriday(49.9));console.log("Is count visible here?", typeof count);

Output · node main.js

Order #1
Order #2
Invoice #1
Order #3
task 1, task 2, task 3
Black Friday price: 34.93
Is count visible here? undefined

Key points

  • A variable only exists in the scope (function or block) where it is declared.
  • A closure is a function that remembers the variables of the scope where it was created.
  • Each call to the outer function creates new private state.
  • let creates a new variable on every loop turn; var does not.

Exercise

Change createCounter so it returns an object with two methods, next() and reset() (which sets count back to zero), and check that resetting the orders does not affect the invoices.