🧠 Lexical Scoping & Closures in JavaScript — Explained Simply

Shaikh AffanShaikh Affan
3 min read

Two words that often scare beginners (and even some experienced devs):
Lexical Scoping and Closures.

But here’s the truth:

If you understand how functions remember where they were created, and what variables were in reach — you've basically cracked both.

Let’s break them down without the buzzwords. You’ll walk away knowing what they are, how they work, and why they matter — with real examples.


🔍 What is Lexical Scope?

Lexical scope (also called static scope) means:

A function’s access to variables is determined by where it’s defined, not where it’s called.

Let’s see that in action:

const name = "Mythic";

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

sayName(); // "Mythic"

That’s straightforward. But here’s where it gets interesting:

function outer() {
  const outerVar = "I’m outside";

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

  inner(); // Can still access `outerVar`
}

outer();

👉 The function inner() was defined inside outer(), so it can see everything in outer()'s scope.

That's lexical scoping. The function remembers the place it was born, and all the variables around it.


🎯 What Is a Closure?

A closure is created when:

A function "remembers" the variables from its lexical scope, even after the outer function has finished running.

Let’s see this “memory” in action:

function makeCounter() {
  let count = 0;

  return function () {
    count++;
    return count;
  };
}

const counter1 = makeCounter();

console.log(counter1()); // 1
console.log(counter1()); // 2

🧠 What's happening here?

  • makeCounter() runs and sets count = 0.

  • Then it returns a function — this function closes over count.

  • Even though makeCounter() is done executing, the returned function remembers count.

That’s a closure.


💡 Real-World Use Cases of Closures

Closures aren’t just some academic concept. They’re used everywhere:

1. Encapsulating state (private variables)

function createUser(name) {
  let score = 0;

  return {
    getScore: () => `${name}'s score: ${score}`,
    increase: () => score++,
  };
}

const user = createUser("Fiza");
user.increase();
console.log(user.getScore()); // Fiza's score: 1

score is private, but still accessible inside returned methods. This is data privacy, powered by closures.


2. In React’s useState (under the hood)

React’s useState is built on closures. When you call setState, it still remembers the old state, thanks to closures.


3. Debouncing / Throttling

Closures help maintain timer state for performance optimizations:

function debounce(fn, delay) {
  let timeoutId;
  return function () {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(fn, delay);
  };
}

🧪 Quick Quiz: What Does This Print?

function outer() {
  const secret = "🧠";
  return function inner() {
    console.log(secret);
  };
}

const func = outer();
func(); // ??

✅ It logs "🧠". Because inner() closed over secret.


🧠 Summary

ConceptMeaning
Lexical ScopeFunction’s access to variables is based on where it’s defined
ClosureFunction remembers variables from the scope where it was created

💬 Final Thought

Closures might sound magical at first — but they’re simply functions remembering their birthplace.
And lexical scoping is the rule that makes that memory possible.

Once these concepts click, you'll start seeing them everywhere — in callbacks, event handlers, frameworks, and custom utilities.

0
Subscribe to my newsletter

Read articles from Shaikh Affan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shaikh Affan
Shaikh Affan

👋 Hey there! I’m a Frontend Developer & UI/UX Enthusiast from Mumbai, currently pursuing my BSc-IT at Kalsekar Technical Campus. I write about web development, Python, and AI, breaking down complex topics into easy-to-understand articles. My goal is to share knowledge, document my learning journey, and help others grow in tech. 🚀 Check out my latest articles and let's learn together!