JavaScript Closures — What Gets Logged?
This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Closure Counter Private State and improve your technical interview readiness.
function outer() {
let count = 0
return function inner() {
count++
return count
}
}
const fn = outer()
console.log(fn())
console.log(fn())
Detailed Explanation
Why This Question Matters
If you've spent any time in JavaScript, you've probably run into closures. On paper, the definition is simple: a function that remembers the environment it was created in. But in practice, closures are where a lot of developers—even those with a few years under their belt—start to trip up.
The snippet we're looking at is a classic interview question. It's designed to test if you actually understand how JavaScript handles scope and memory, or if you're just guessing based on how other languages work. If you can't pinpoint exactly why a variable persists after a function has finished executing, you're going to struggle with things like React hooks, event listeners, and asynchronous patterns.
Understanding the Code
Let's look at the snippet again:
At first glance, it looks like a standard function returning another function. But the magic is in the count variable.
When we call outer(), JavaScript creates a new execution context. It initializes count to 0 and then returns the inner function. Normally, when a function finishes running, all its local variables are wiped from memory (garbage collected). That's how standard functions work.
However, inner is defined *inside* outer. Because inner references count, JavaScript realizes that inner still needs that variable even after outer has returned. Instead of deleting count, the engine keeps it alive in a special "closure" scope.
When we do const fn = outer(), fn now holds a reference to the inner function, and that function carries its own private little backpack containing the count variable.
Finding the Correct Answer
The correct answer is Option B: 1, 2.
Here is the play-by-play:
1. const fn = outer(): We execute outer. It sets count = 0 and returns the inner function. fn is now that inner function.
2. First console.log(fn()): We call fn. It goes into its "backpack," finds count (which is 0), increments it to 1, and returns it. The log prints 1.
3. Second console.log(fn()): We call fn again. Crucially, it doesn't start over. It's using the *same* closure. It finds count (which is now 1), increments it to 2, and returns it. The log prints 2.
If the answer were 1, 1, it would mean count was being reset every time. But since fn is a persistent reference to the same closure, the state is preserved.
Common Mistakes Developers Make
The biggest mistake is assuming that calling fn() is the same as calling outer().
Some developers think that because outer() is where count is defined, every time they call fn(), the whole process starts from the top. That's not how it works. outer() was only called once. Everything after that is just interacting with the returned inner function.
Another common point of confusion is the difference between let, const, and var in this context. While var would behave similarly here, using let ensures the variable is block-scoped, which is the modern standard.
Also, don't confuse this with "global variables." count isn't global; it's private. You can't access count from outside the fn() call. If you try to console.log(count) in the global scope, you'll get a ReferenceError. That's the power of closures—they provide a way to have "private" state without using classes.
Real-World Usage
You see this pattern everywhere in production code, even if it's not written exactly like this.
1. Function Currying
In functional programming, we often use closures to create specialized functions. For example, a function that creates a logger with a specific prefix:
2. State Management in React
If you use React, useState is essentially a closure under the hood. React needs to remember your state across multiple re-renders of a component. It stores that value in a way that the component function can access it every time it runs.
3. Module Pattern
Before ES modules (import/export) became standard, developers used "Immediately Invoked Function Expressions" (IIFEs) and closures to hide private variables from the global window object, preventing naming collisions in large apps.
Key Takeaways
- A closure is created when a function is defined inside another function and references a variable from the parent's scope.
- The inner function "closes over" the variable, preventing it from being garbage collected.
- Closures allow for persistent state without polluting the global namespace.
- Calling the outer function creates a *new* closure. If you created const fn2 = outer(), it would start its own count from 0, independent of fn.
Why this matters
Understanding Closure Counter Private State is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.