[Advanced JS] What is a Closure? Advanced Applications of Closures in JavaScript

If you have ever browsed programming forums or attended JavaScript interviews, you have surely heard the keyword "Closure." It is often described as an abstract, tricky concept, but also the key to becoming a professional JavaScript developer.

What is a Closure? Advanced Applications of Closures in JavaScript

So, what exactly is a closure? Why is it so important? And how can we not only understand it but also use it proficiently? Let's unveil this mystery together!

1. What is a Closure? An Easy-to-Understand Definition 💡

Forget the complicated academic definitions for a moment. Imagine this:

A function is like a worker. When created, this worker is given a backpack. Inside that backpack are all the "tools" (variables, constants) the worker might need, taken from the place where he was "born." Even if the worker is sent to work somewhere else, he always carries that backpack and can use the tools inside.

A closure is the combination of that function and the backpack (lexical scope) it carries.

Explaining the concept of Closure in JavaScript

Now, let's get a bit more formal:

A closure is the combination of a function and the lexical environment where that function was declared. A closure allows a child function to access and manipulate the variables of its parent function, even after the parent has finished executing.

Still a bit confusing? Don't worry, let's look at the classic example below.

function createCharacter() {
  let characterName = 'Luffy' // Variable defined in the parent function

  function showName() {
    // Child function
    console.log(characterName) // Can access the parent's variable
  }

  return showName // Return the child function
}

// Call createCharacter, which returns the showName function
const callName = createCharacter()

// At this point, createCharacter has finished executing.
// In theory, 'characterName' should be removed from memory.

// BUT...
callName() // Output: "Luffy"

What magic just happened? When createCharacter is called, it returns the showName function. When showName was created, it "closed over" its environment, including the variable characterName. It carries the "backpack" containing characterName with it. So, even though createCharacter has finished, callName (which is showName) still remembers and can access characterName. That's a closure!

2. Why are Closures Important? The Real Power 🧠

Closures are not just a theoretical puzzle. They are the foundation for many design patterns and powerful features in JavaScript.

🔐 Data Encapsulation - Hiding Data and Creating Private Variables

In many object-oriented languages, you have keywords like private to protect data. JavaScript (historically) did not have this concept officially, and closures are the solution.

Let's look at a counter example:

function createCounter() {
  let count = 0 // The 'count' variable is "private"

  return {
    increment: function () {
      count++
      console.log(count)
    },
    decrement: function () {
      count--
      console.log(count)
    },
    getValue: function () {
      return count
    },
  }
}

const counter = createCounter()

counter.increment() // Output: 1
counter.increment() // Output: 2
counter.decrement() // Output: 1

// You cannot access 'count' directly from outside
console.log(counter.count) // Output: undefined

Here, the count variable lives inside createCounter. We cannot access or change it from the outside. The only way to interact with count is through the increment, decrement, and getValue methods returned. These methods form a closure, "remembering" and sharing the same count variable. This is data hiding in action.

🏭 Function Factories

Closures let you create pre-configured functions.

function makeGreeter(greeting) {
  return function (name) {
    console.log(`${greeting}, ${name}!`)
  }
}

const sayHello = makeGreeter('Hello')
const sayXinChao = makeGreeter('Xin chào')

sayHello('John') // Output: Hello, John!
sayXinChao('Son') // Output: Xin chào, Son!

The makeGreeter function is a "factory." Each time you call it, you create a new function (sayHello, sayXinChao) that "remembers" a different greeting value.

⏳ Callbacks and Asynchronous Programming

This is one of the most common uses of closures. When you work with setTimeout, event listeners, or Promises, you are using closures all the time, often without realizing it.

function waitAndSay(message, delay) {
  setTimeout(function () {
    // This callback is a closure
    // It "remembers" the 'message' variable from the outer scope
    console.log(message)
  }, delay)
}

waitAndSay('Wait 3 seconds and I will appear.', 3000)

The callback passed to setTimeout will be executed after 3 seconds. At that point, waitAndSay has long finished running. But thanks to the closure, the callback still "remembers" the value of message when it was created.

3. Common Pitfall: Loops and Closures ⚠️

This is a classic example that often appears in interviews and confuses many developers.

for (var i = 1; i <= 5; i++) {
  setTimeout(function () {
    console.log(i)
  }, i * 1000)
}

Many expect the result to be 1, 2, 3, 4, 5 printed every second. But the actual result is: 6 6 6 6 6

Why?

  • var is function-scoped: There is only one i variable shared by all iterations.
  • Asynchronous: The for loop runs very quickly and finishes almost immediately. It queues up 5 setTimeout calls. When the loop ends, the value of i is 6.
  • Closure: After 1 second, 2 seconds, etc., when the callbacks in setTimeout are executed, they access the variable i. Since they all reference the same i variable, they all see its final value, which is 6.

How to fix it?

Use let: This is the modern and simplest way. let is block-scoped, meaning each iteration of the for loop creates a new i variable.

for (let i = 1; i <= 5; i++) {
  setTimeout(function () {
    // Each callback now has its own closure with its own 'i'
    console.log(i)
  }, i * 1000)
}
// Result: 1, 2, 3, 4, 5 (as expected)

Conclusion: Closures Are Not "Magic" 🔮

Closures are not something too mysterious. They are a natural consequence of how JavaScript handles variable scope (specifically, Lexical Scoping—scope determined at code writing time, not runtime).

Mastering closures will help you:

  • Write cleaner, more modular code.
  • Deepen your understanding of core concepts like scope and context.
  • Confidently tackle advanced design patterns and modern JavaScript frameworks.

Hopefully, after this article, "Closure" is no longer a scary keyword, but a powerful tool in your JavaScript skillset. Practice, experiment with examples, and you will see its amazing power.

Good luck!

Related Posts

[Advanced JS] What is an IIFE? Advanced Applications of IIFE in JavaScript

Learn about IIFE (Immediately Invoked Function Expression) and how it works in JavaScript. Discover practical uses of IIFE for creating scope and protecting variables.

[JS Basics] Distinguishing Shallow Copy and Deep Copy in JavaScript

Are you sure you’re copying objects the right way? Dive deep into Shallow copy and Deep copy in JavaScript with illustrative examples, and choose the right method for your application.

[JS Basics] ES6+ Features: What You Need to Know to Write Modern JavaScript

This article is the most detailed handbook on JavaScript Events. Learn how to listen for and respond to user actions on your website, helping you build more interactive and smoother web applications.

[JS Basics] What is JavaScript? Why is it Important for Developers?

What is JavaScript? This article explains in detail the concept, role, and applications of JavaScript—the indispensable programming language in modern web development.