Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
⚡ Days 15–22 – Advanced & Async JavaScript  ·  Day 16

Day 16: Scope, Hoisting & Closures

⏱ 90 min📶 Intermediate🎯 100% self-study
Day 16 / 30

✅ Today you will learn

  • Global, function and block scope; the scope chain
  • Hoisting: var vs let/const vs function declarations, the TDZ
  • Closures: functions that remember
  • Real uses: private state, factories, memoise, once
  • The famous var-in-a-loop bug

🏢 In the real world

“Explain closures” is one of the most asked JavaScript interview questions in India, from service companies to product startups. It is also practical: every event handler, timer and React hook relies on closures. Today you will understand them well enough to explain with a sweet-shop example – and fix the bug that has confused developers for 20 years.

Scope and the scope chain

JavaScript
const shop = 'Sharma Sweets';                 // global scope

function makeInvoice(orderNo) {              // function scope
  const prefix = 'INV';
  if (orderNo > 1000) {
    const series = 'B';                      // block scope
    return `${shop} ${prefix}-${series}${orderNo}`;   // looks outward: block → function → global
  }
  return `${shop} ${prefix}-${orderNo}`;
}
console.log(makeInvoice(42));
console.log(makeInvoice(1042));
console.log(typeof prefix, typeof series);  // not visible outside
Sharma Sweets INV-42 Sharma Sweets INV-B1042 undefined undefined
Block scope { }Function scopeModule / script scopeGlobal scopeNot found → ReferenceError
JavaScript looks for a variable from the inside out

Hoisting and the temporal dead zone

JavaScript
console.log(greet('Riya'));          // ✅ function declarations are hoisted with their body
function greet(n) { return 'Namaste ' + n; }

console.log(oldStyle);               // undefined – var is hoisted but set to undefined
var oldStyle = 'var';

try {
  console.log(modern);               // ❌ let/const exist but are in the "temporal dead zone"
  let modern = 'let';
} catch (err) {
  console.log(err.name + ': ' + err.message);
}
Namaste Riya undefined ReferenceError: Cannot access 'modern' before initialization
DeclarationHoisted?Before the line
function name() {}Yes, with bodyCan be called
var xYes, as undefinedundefined (silent bugs)
let / constYes, but uninitialised (TDZ)ReferenceError
const f = () => {}Like constReferenceError
class A {}Like let (TDZ)ReferenceError

Closures: functions that remember

JavaScript
function createCounter(label) {
  let count = 0;                       // private – nobody outside can touch it
  return function () {                 // this inner function "closes over" count and label
    count++;
    return `${label}: token #${count}`;
  };
}

const counterA = createCounter('Counter A');
const counterB = createCounter('Counter B');
console.log(counterA());
console.log(counterA());
console.log(counterB());               // separate memory for each counter
console.log(counterA());
console.log(typeof count);
Counter A: token #1 Counter A: token #2 Counter B: token #1 Counter A: token #3 undefined
🧠 The one-line definition

A closure is a function bundled with the variables of the place where it was created. Even after createCounter has finished, the returned function still has access to its own count – like a shop token machine that keeps its own number.

Real uses of closures

JavaScript
// 1. Private state with a public API (module pattern)
function createWallet(start) {
  let balance = start;
  return {
    pay(amount) {
      if (amount > balance) return `Insufficient balance (₹${balance})`;
      balance -= amount;
      return `Paid ₹${amount}, left ₹${balance}`;
    },
    get balance() { return balance; },
  };
}
const wallet = createWallet(1000);
console.log(wallet.pay(650));
console.log(wallet.pay(500));
wallet.balance = 999999;                       // no setter → cannot be changed from outside
console.log(wallet.balance);

// 2. Function factories
const makeDiscount = percent => price => Math.round(price * (1 - percent / 100));
const diwali10 = makeDiscount(10), member5 = makeDiscount(5);
console.log(diwali10(900), member5(900));

// 3. once() – e.g. a coupon that can be applied only once
function once(fn) {
  let done = false, result;
  return (...args) => (done ? result : ((done = true), (result = fn(...args))));
}
const applyCoupon = once(code => `Coupon ${code} applied`);
console.log(applyCoupon('SWEET10'), '|', applyCoupon('AGAIN'));

// 4. Memoisation – cache slow results
function memo(fn) {
  const cache = new Map();
  return n => (cache.has(n) ? cache.get(n) + ' (cached)' : (cache.set(n, fn(n)), cache.get(n)));
}
const slowGst = memo(amount => amount * 0.05);
console.log(slowGst(2000), slowGst(2000));
Paid ₹650, left ₹350 Insufficient balance (₹350) 350 810 855 Coupon SWEET10 applied | Coupon SWEET10 applied 100 100 (cached)

The classic var-in-a-loop bug

JavaScript
const withVar = [], withLet = [];
for (var i = 1; i <= 3; i++) withVar.push(() => i);   // ONE shared i for all functions
for (let j = 1; j <= 3; j++) withLet.push(() => j);   // a NEW j for every round
console.log(withVar.map(f => f()));    // [4, 4, 4] ❌
console.log(withLet.map(f => f()));    // [1, 2, 3] ✅
[ 4, 4, 4 ] [ 1, 2, 3 ]
💡 Why it happens

var is function-scoped, so all three closures remember the same variable, which is 4 after the loop ends. let creates a fresh binding for each iteration. This is the #1 reason modern code uses let/const.

Real task: independent token counters

HTML + JavaScript
<div id="counters"></div>

<script>
function createTokenCounter(name) {
  let next = 1;                                     // private per counter
  const box = document.createElement('div');
  box.style.cssText = 'display:inline-block;margin:6px;padding:12px 16px;border:1px solid #e5e7eb;border-radius:12px;text-align:center';
  const title = document.createElement('b');
  title.textContent = name;
  const shown = document.createElement('div');
  shown.style.fontSize = '2rem';
  shown.textContent = '—';
  const btn = document.createElement('button');
  btn.textContent = 'Next token';
  btn.addEventListener('click', () => { shown.textContent = 'A' + String(next++).padStart(3, '0'); });   // closure!
  box.append(title, shown, btn);
  return box;
}
document.querySelector('#counters').append(
  createTokenCounter('Sweets counter'),
  createTokenCounter('Namkeen counter'),
  createTokenCounter('Billing'));
</script>
▶  Each click handler remembers its own “next” – three closures

🏅 Job skills unlocked today

✔ Trace variables through the scope chain✔ Predict hoisting behaviour and avoid the TDZ✔ Write closures for private state, factories, once and memo✔ Explain and fix the var-in-a-loop bug

Practice tasks

🏋️ Practice – do it yourself

Task 1: Write createRateLimiter(limit) that returns a function allowing at most limit calls, then returns "Too many requests".
Show solution
JavaScript
function createRateLimiter(limit) {
  let calls = 0;
  return function (action) {
    calls++;
    return calls <= limit ? `OK: ${action} (${calls}/${limit})` : 'Too many requests';
  };
}
const sendOtp = createRateLimiter(3);
['otp', 'otp', 'otp', 'otp'].forEach(a => console.log(sendOtp(a)));
OK: otp (1/3) OK: otp (2/3) OK: otp (3/3) Too many requests
Task 2: Predict the output, then run it.
Show solution
JavaScript
let x = 'global';
function outer() {
  let x = 'outer';
  function inner() { return x; }
  return inner;
}
const f = outer();
x = 'changed global';
console.log(f());          // 'outer' – inner remembers outer's x, not the global one
outer
Task 3: Create a makeMultiplier(n) factory and use it to build a GST-5% and GST-12% calculator.

Quick quiz

🧠 Quick Quiz – test yourself

1. A closure is…
It remembers its scope.
2. Accessing a let variable before its line gives…
TDZ.
3. Which is hoisted with its body?
Declarations only.
4. for (var i = 0; i < 3; i++) fns.push(() => i); fns[0]() returns…
Shared var.
5. Closures help create…
Encapsulation.

Interview corner

💼 Interview Corner

What is a closure? Give a practical example.

A closure is the combination of a function and its lexical environment, so the function keeps access to outer variables after the outer function returns. Example: a counter/rate-limiter with private state, event handlers that remember their item, or a memoised function with a private cache.

Explain hoisting.

During compilation declarations are registered in their scope: function declarations fully, var as undefined, let/const/class as uninitialised (temporal dead zone) – so they exist but cannot be read before the declaration line.

Summary

  • Variables are looked up from block → function → global.
  • Functions hoist fully; var as undefined; let/const are in the TDZ.
  • Closures = functions + remembered variables → private state, factories, once, memo.
  • Use let in loops to avoid the shared-var bug.
➡️ Next

Day 17: this, classes and object-oriented JavaScript – build a reusable Cart class.

Frequently Asked Questions

Do closures cause memory leaks?
Only if you keep references to big objects you no longer need (e.g. listeners never removed). Normal closures are freed when nothing references them.
Where do I use closures in React?
Every component function, event handler and hook callback is a closure over props and state – which is why “stale state” bugs happen when a closure captures an old value.
← JavaScript (30 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 17: this, Classes & Object-Oriented JS Your progress: 16 / 30 (53%) · Lesson 17 / 30 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free