Ticker

6/recent/ticker-posts

Chapter 12: Closures & Event Loop Explained Through a Banking and ATM Analogy

 

The JavaScript Interview Question That Terrifies Beginners

If you spend enough time learning JavaScript, sooner or later someone will ask:

"Can you explain closures?"

For many beginners, this question feels scary.

Not because closures are impossible to understand.

But because most explanations make them sound more complicated than they really are.

You'll often hear definitions like:

"A closure is a combination of a function bundled together with references to its surrounding state."

Technically correct?

Yes.

Helpful for a beginner?

Not really.

When I first read that definition, I understood every English word individually.

But I still had no idea what a closure actually was.

So let's forget technical definitions for a moment.

Let's start with a simple banking story.

Because once you understand the story, closures become surprisingly easy.


Chapter 12: Closures & Event Loop Explained Through a Banking and ATM Analogy


The ATM Card Story

Imagine you open a bank account.

The bank creates:

Account Number
Balance
Customer Information

Suppose your account balance is:

₹50,000

Now you leave the bank.

Hours later, you visit an ATM.

Something interesting happens.

Even though you left the bank long ago, the ATM still knows:

Your account
Your balance
Your details

The ATM remembers information that was created earlier.

That's the key idea behind closures.

A closure allows a function to remember variables from its parent scope even after the parent function has finished executing.

Read that sentence again.

That's the entire concept.

Everything else is simply understanding how JavaScript makes it happen.


The Problem Closures Solve

Let's imagine JavaScript had no closures.

Consider:

function createUser() {

    let username = "Aqad";

}

When the function finishes:

username disappears

This is what we learned in the Scope chapter.

Local variables belong to their function.

After execution completes, those variables should normally be gone.

But sometimes we want a function to remember information.

For example:

  • User sessions

  • Counters

  • Private variables

  • Shopping carts

  • Authentication data

Closures make this possible.


Your First Closure Example

Let's look at a simple example.

function outer() {

    let name = "Aqad";

    function inner() {
        console.log(name);
    }

    return inner;
}

const result = outer();

result();

Output:

Aqad

At first glance, this looks normal.

But something unusual happened.


What Makes This Strange?

Let's analyze carefully.

When:

outer();

finishes executing,

normally:  name

should disappear.

Because it belongs to:

outer()

Right?

But later:

result();

still prints: Aqad

How?

Where did JavaScript get that value from?

The answer is: Closure

The inner function remembered its parent scope.


Visualizing What Happens

Step 1: outer();

creates: name = Aqad

Step 2: JavaScript creates:

inner()

Step 3: The inner function is returned.

Step 4: Even though:

outer()

has finished,

JavaScript keeps:

name = Aqad

alive because the returned function still needs it.

This preserved connection is called a closure.


Think About the ATM Again

Bank:

Creates Account
Creates Balance
Stores Information

You leave.

Later:

ATM accesses account details.

Why?

Because the ATM maintains a connection to the account.

Similarly:  inner()

maintains access to variables from:

outer()

even after execution finishes.


A More Practical Example

Imagine we want to count button clicks.

Without closures:

let count = 0;

function increment() {
    count++;
    console.log(count);
}

Works.

But:

count

is global.

Any code can change it.

That isn't ideal.

Let's use a closure.


Counter Using Closure

function createCounter() {

    let count = 0;

    return function() {

        count++;

        console.log(count);

    };

}

const counter = createCounter();

counter();
counter();
counter();

Output:

1
2
3

Why This Is Amazing

Notice:

createCounter()

runs only once.

Yet: 

count

continues updating.

The variable survives because of the closure.

Without closures:

count would reset every time

But because the returned function remembers its environment:

count stays alive

This is one of the most powerful features in JavaScript.


A Real ATM Example in Code

Let's build a simple bank account.

function bankAccount() {

    let balance = 5000;

    return function() {

        console.log(balance);

    };

}

const checkBalance = bankAccount();

checkBalance();

Output:

5000

The balance remains accessible.

Even though:

bankAccount()

finished long ago.


Depositing Money

Let's improve it.

function bankAccount() {

    let balance = 5000;

    return function(amount) {

        balance += amount;

        console.log(balance);

    };

}

const deposit = bankAccount();

deposit(1000);
deposit(2000);
deposit(500);

Output:

6000
8000
8500

The function remembers the balance.

Exactly like an ATM remembering your account information.


Why Developers Love Closures

Closures allow us to create private data.

Consider:

function createUser() {

    let password = "secret123";

}

Normally:

Anyone inside the function can access:

password

But we may not want outside code to modify it.

Closures help protect private information.


Creating Private Variables

Example:

function createAccount() {

    let balance = 10000;

    return {

        showBalance() {
            console.log(balance);
        }

    };

}

const account = createAccount();

account.showBalance();

Output:

10000

But:

console.log(balance);

Output:

ReferenceError

The variable remains private.

Only approved methods can access it.


Real World Example: Shopping Cart

Imagine an e-commerce website.

Customer adds products.

We need to remember cart information.

Closure example:

function createCart() {

    let items = 0;

    return function() {

        items++;

        console.log(
            "Items in cart:",
            items
        );

    };

}

const addToCart = createCart();

addToCart();
addToCart();
addToCart();

Output:

Items in cart: 1
Items in cart: 2
Items in cart: 3

The cart remembers its state.

This pattern appears everywhere in modern applications.


Closures and Event Handlers

Whenever a user clicks:

Add to Cart
Login
Buy Now
Submit

closures are often involved behind the scenes.

Many UI frameworks such as React depend heavily on closure behavior.

Even if developers don't realize it.


How JavaScript Creates a Closure

Let's revisit:

function outer() {

    let name = "Aqad";

    function inner() {

        console.log(name);

    }

    return inner;

}

JavaScript notices:

inner()

depends on:

name

Therefore:

JavaScript preserves that variable.

Even after:

outer()

completes.

The relationship remains alive.

This preserved relationship is the closure.


A Simpler Definition

After seeing several examples, we can finally define closures properly.

A closure is created when:

A function remembers variables
from its parent scope
even after the parent function
has finished execution.

That's it.

No complicated wording required.


Common Beginner Mistake

Many beginners think:

Closure = Function Inside Function

Not exactly.

A nested function alone is not necessarily a closure.

A closure happens when:

The inner function
continues accessing
the outer function's variables.

That memory connection is the important part.


Another Interview Example

What is the output?

function test() {

    let count = 0;

    return function() {

        count++;

        console.log(count);

    };

}

const increment = test();

increment();
increment();
increment();

Output:

1
2
3

Why?

Because the closure preserves:

count

between executions.


Why Closures Matter in Real Applications

Closures are used in:

  • Authentication systems

  • User sessions

  • Shopping carts

  • React hooks

  • Event listeners

  • API request handlers

  • Data privacy patterns

  • Caching systems

  • Timers and intervals

Even if you never intentionally create a closure, you'll use them constantly as a JavaScript developer.


The Memory Side of Closures

Closures are powerful.

But developers must use them carefully.

Remember:

JavaScript keeps referenced variables alive.

If large objects remain referenced unnecessarily:

Memory usage increases.

This can create memory leaks in large applications.

Most beginners don't face this problem.

But experienced developers keep it in mind.


Why Senior Developers Care About Closures

Closures are one of the clearest signs that a developer understands JavaScript deeply.

Because closures connect several concepts we've already learned:

  • Execution Context

  • Scope

  • Lexical Environment

  • Scope Chain

  • Function Execution

Closures are where all those ideas come together.

Once closures click, many advanced JavaScript topics suddenly become easier.


 Summary we learned:

  • What closures are

  • Why closures exist

  • ATM and bank account analogy

  • Remembering variables after execution

  • Counter examples

  • Private variables

  • Shopping cart example

  • Event handler usage

  • Real-world applications

  • Common interview questions

Think of a closure like an ATM card.

You leave the bank.

The bank session ends.

But the ATM still remembers your account.

Similarly:

A function may finish execution.

But another function can still remember and access its variables.

That memory is the closure.

And now that we understand how JavaScript remembers information, it's time to understand how JavaScript handles something even more mysterious:

Tasks that take time.

API requests.

Timers.

User clicks.

Background operations.

To understand those, we need to learn one of the most famous topics in JavaScript.


Event Loop Explained Through a Restaurant and Waiter Analogy

At some point in every developer's journey, a strange question appears.

You learn that JavaScript is:

Single Threaded

Then you hear people say:

JavaScript handles thousands of users.
JavaScript handles API requests.
JavaScript handles timers.
JavaScript handles button clicks.
JavaScript handles file uploads.

And naturally you ask:

"Wait a minute..."

"If JavaScript can only do one thing at a time, how is all of this possible?"

It's a very reasonable question.

In fact, when I first learned JavaScript, this concept confused me more than closures.

Because on the surface, the two statements seem to contradict each other.

JavaScript does one thing at a time.

and

JavaScript handles many things simultaneously.

Both statements are true.

The secret lies in understanding:

  • The Call Stack

  • Web APIs

  • Callback Queue

  • Event Loop

Before we dive into technical terms, let's start with something much easier to imagine.

A restaurant.


The Restaurant Story

Imagine you walk into a restaurant.

There is:

  • One waiter

  • One kitchen

  • Many customers

The waiter receives orders.

Customer 1 says:

I want a burger.

Customer 2 says:

I want a pizza.

Customer 3 says:

I want pasta.

Now imagine the waiter behaves like this:

Take burger order
Go to kitchen
Stand there 20 minutes
Wait for burger
Return
Take next order

The restaurant would be terrible.

Customers would become frustrated.

Business would fail.

Good restaurants don't work this way.

Instead:

Take order
Send order to kitchen
Move to next customer

The waiter remains available.

The kitchen works in the background.

This is almost exactly how JavaScript works.


JavaScript Is The Waiter

Think of JavaScript as the waiter.

The Call Stack is the waiter's current task list.

The browser or Node.js environment acts like the kitchen.

Whenever JavaScript encounters a task that takes time:

API Request
Timer
Database Query
File Reading
User Click

JavaScript delegates that task.

Then continues serving other work.

This delegation mechanism is the foundation of asynchronous programming.


First Example

Consider:

console.log("Start");

setTimeout(() => {
    console.log("Timer Finished");
}, 2000);

console.log("End");

Many beginners expect:

Start
(wait 2 seconds)
Timer Finished
End

But the actual output is:

Start
End
Timer Finished

Why?

Because JavaScript doesn't wait.

Just like a good waiter doesn't stand beside the oven waiting for food.


What Actually Happens?

Let's break it down.

Step 1

JavaScript executes:

console.log("Start");

Output:

Start

Step 2

JavaScript reaches:

setTimeout(...)

Instead of handling the timer itself:

It gives the task to the browser.

Think:

Waiter → Kitchen

The timer starts running elsewhere.

JavaScript immediately continues.


Step 3

JavaScript executes:

console.log("End");

Output:

End

Step 4

After 2 seconds:

The browser says:

Timer complete.

Now the callback becomes ready.

Eventually:

console.log("Timer Finished");

executes.

Output:

Timer Finished

Understanding the Components

To understand the Event Loop properly, we need to know four parts.

Call Stack
Web APIs
Callback Queue
Event Loop

Let's examine each one.


Part 1: Call Stack

We learned this in the previous chapter.

The Call Stack handles:

Function Calls
Execution Contexts
Current Tasks

Think of it as the waiter's hands.

The waiter can hold only one active task at a time.


Part 2: Web APIs

Web APIs are provided by the browser.

Examples:

setTimeout()
fetch()
addEventListener()

Important:

These are NOT part of JavaScript itself.

The browser provides them.

Think of Web APIs as:

Kitchen Staff

The waiter delegates work to them.


Part 3: Callback Queue

When a background task finishes:

Its callback does not immediately execute.

Instead:

It enters a waiting area.

This waiting area is called:

Callback Queue

Restaurant analogy:

Finished Orders Shelf

Food is ready.

But the waiter hasn't picked it up yet.


Part 4: Event Loop

Now we meet the star of the chapter.

The Event Loop.

Its job is surprisingly simple.

It constantly checks:

Is the Call Stack empty?

If:

YES

Then:

Move callback from queue
to Call Stack

That's it.

That's the Event Loop's entire responsibility.


Visualizing Everything Together

Imagine:

console.log("Start");

setTimeout(() => {
    console.log("Timer");
}, 1000);

console.log("End");

Flow:

Start

↓

Call Stack

↓

setTimeout

↓

Browser API

↓

Timer Starts

↓

End

↓

Call Stack Empty

↓

Timer Finished

↓

Callback Queue

↓

Event Loop

↓

Call Stack

↓

Timer

Output:

Start
End
Timer

Why JavaScript Doesn't Freeze

Imagine JavaScript handled timers directly.

Example:

setTimeout(() => {
    console.log("Done");
}, 10000);

If JavaScript waited 10 seconds:

The page would freeze.

Buttons wouldn't work.

Scrolling wouldn't work.

Everything would stop.

Instead:

JavaScript delegates the task.

The application remains responsive.

This is why modern web applications feel smooth.


Real World Example: Food Delivery App

Imagine a customer opens a food delivery app.

The app requests restaurant data.

fetch("/restaurants");

Fetching data may take:

500 ms
2 seconds
5 seconds

JavaScript doesn't wait.

Instead:

Send Request
Continue Running

The user can:

  • Scroll

  • Search

  • Click buttons

While the request is being processed.

When the response arrives:

The callback executes.

This creates a much better user experience.


Understanding User Click Events

Consider:

button.addEventListener("click", () => {
    console.log("Button Clicked");
});

What happens?

JavaScript registers the event.

Then continues running.

It doesn't sit there waiting for a click.

That would be wasteful.

Instead:

The browser monitors clicks.

When a click occurs:

The callback enters the queue.

The Event Loop eventually executes it.


Why Event Loop Is So Important

Without the Event Loop:

Modern web applications would be impossible.

Think about:

  • WhatsApp Web

  • Gmail

  • YouTube

  • Facebook

  • Amazon

  • AQAD Marketplace

All these applications perform:

Network Requests
Notifications
Real-Time Updates
User Interactions
Background Tasks

The Event Loop coordinates all of it.


The Famous Interview Question

What is the output?

console.log("A");

setTimeout(() => {
    console.log("B");
}, 0);

console.log("C");

Many beginners answer:

A
B
C

Actual output:

A
C
B

Why?

Because:

Even a zero-millisecond timer goes through:

Web API
↓
Queue
↓
Event Loop
↓
Call Stack

The callback still waits its turn.


Another Example

console.log("Start");

setTimeout(() => {
    console.log("Timer");
}, 0);

console.log("Middle");

console.log("End");

Output:

Start
Middle
End
Timer

The timer callback executes only after the Call Stack becomes empty.


AQAD Marketplace Example

Imagine a retailer searches for products.

searchProducts();

The application sends a request:

fetch("/products");

JavaScript continues running.

Meanwhile:

Server Searches Products
Database Executes Query
Results Generated

When data arrives:

Callback Queue
↓
Event Loop
↓
Call Stack

Products appear on screen.

All without freezing the application.


Why Beginners Struggle With The Event Loop

Because the code appears sequential.

Example:

console.log("1");

setTimeout(() => {
    console.log("2");
}, 1000);

console.log("3");

Many people read:

1
Wait
2
3

But JavaScript actually does:

1
3
2

The Event Loop changes the execution order.

Understanding this is the key to understanding asynchronous JavaScript.


Event Loop in One Sentence

If you remember only one thing from this chapter, remember this:

The Event Loop continuously checks whether the Call Stack is empty and moves completed callbacks into execution when JavaScript is ready.

Everything else is simply supporting detail.


Common Beginner Mistakes

Mistake 1

Thinking JavaScript waits for timers.

It doesn't.


Mistake 2

Thinking setTimeout(0) executes immediately.

It doesn't.

It still enters the queue.


Mistake 3

Thinking Web APIs belong to JavaScript.

They don't.

The browser or Node.js environment provides them.


Mistake 4

Thinking callbacks execute the moment they're ready.

They don't.

They wait until the Call Stack becomes empty.


Why Senior Developers Care About The Event Loop

The Event Loop explains:

  • Async behavior

  • Timers

  • API requests

  • User interactions

  • Performance issues

  • UI responsiveness

Many difficult JavaScript bugs become easier once you understand how the Event Loop works.

In fact:

Promises.

Async/Await.

Fetch.

Node.js.

React.

Almost every modern JavaScript technology relies on Event Loop behavior.


 Summary we learned:

  • Why JavaScript appears asynchronous

  • The Restaurant and Waiter analogy

  • Call Stack

  • Web APIs

  • Callback Queue

  • Event Loop

  • Timer execution

  • User events

  • API requests

  • Common interview questions

Think of JavaScript as a smart waiter.

A bad waiter waits beside the oven.

A smart waiter delegates work and keeps serving customers.

JavaScript does the same thing.

It delegates long-running tasks, keeps working, and uses the Event Loop to know when completed tasks are ready.

And now that we understand how JavaScript handles asynchronous operations, it's time to learn one of the most important tools built on top of that system.

A tool that solved a major problem in JavaScript development.

A tool called:


Post a Comment

0 Comments