Anik Sikder
Technical Writing/javascript/scopes-closures-and-decorators-in-javascript-explained-clearly
article.sh

$ open article

javascript

Scopes, Closures, and Decorators in JavaScript Explained

9 min readSeptember 1, 2025
JavaScript Scopes Closures and Decorators Visualization

Scopes, Closures, and Decorators in JavaScript Explained 🧠

Hey JavaScript developers! 👋

Have you ever wondered:

  • Why can an inner function access variables from its parent function?
  • How does JavaScript know where to find a variable?
  • Why do closures seem to "remember" values forever?
  • How do decorators magically add new behavior to existing functions?

If you've ever asked any of these questions, you're about to unlock one of the most important pieces of JavaScript.

Today we're diving into:

  • Scopes
  • Closures
  • Decorators

And more importantly, what happens behind the scenes inside the JavaScript engine.

Ready?

Let's pull back the curtain. 🎭


The Castle of Variables: Understanding Scope

Every variable in JavaScript lives somewhere.

That "somewhere" is called its scope.

Scope determines:

  • Who can access a variable
  • Where a variable exists
  • When a variable gets destroyed

Think of scope like rooms inside a castle.

Some variables live in public areas.

Some live in private rooms.

Some are only accessible inside tiny hidden chambers.


JavaScript Uses Lexical Scope

JavaScript is a lexically scoped language.

This means:

Where you write the code determines what variables are accessible.

Not where the function is called.

Example:

code
const globalVar = "🌎";

function outer() {
  const outerVar = "🚪";

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

  inner();
}

outer();

Output:

code
🌎
🚪

Why?

Because inner() was written inside outer().

It automatically gains access to everything in its surrounding scope.


The Four Levels of Scope

Global Scope 🌍

Variables declared outside any function or block.

code
const siteName = "My App";

Accessible everywhere.


Function Scope 🏛️

Every function creates its own scope.

code
function greet() {
  const message = "Hello";
}

Outside code cannot access message.


Block Scope 🚪

Created by:

code
{
}

When using:

code
let
const

Example:

code
if (true) {
  let secret = "Hidden";
}

console.log(secret);

Output:

code
ReferenceError

Module Scope 📦

Modern ES Modules create their own top-level scope.

code
export const apiUrl = "...";

Variables don't leak into the global namespace.

This is one reason modern JavaScript is much cleaner.


Behind the Scenes: Execution Context

Whenever JavaScript executes code, it creates an Execution Context.

Think of it as a workspace for the currently running code.

Every execution context contains:

Variable Environment

Where variables live.

Scope Chain

References to outer scopes.

This Binding

The current value of this.


Example:

code
function outer() {
  const outerVar = "🚪";

  function inner() {
    const innerVar = "🗝️";

    console.log(outerVar);
  }

  inner();
}

When inner() executes:

code
inner Context
     ↓
outer Context
     ↓
Global Context

This chain is called the Scope Chain.


Closures: Functions That Remember

Now things get interesting.

A closure is created when a function remembers variables from its surrounding scope even after that scope has finished executing.

Let's see it.

code
function counter() {
  let count = 0;

  return function increment() {
    count++;

    console.log(count);
  };
}

const add = counter();

add();
add();
add();

Output:

code
1
2
3

Wait...

counter() already finished executing.

Why is count still available?

That's exactly what a closure is.


Closures Are Like Memory Jars 🫙

Imagine the inner function carries a jar containing all the variables it still needs.

When counter() finishes:

code
count = 0

doesn't disappear.

Instead:

code
increment()
     ↓
Closure Memory
     ↓
count

As long as increment exists, the closure survives.


Behind the Scenes: How Closures Work

Let's break it down.

Step 1

counter() executes.

A new execution context is created.

code
count = 0

lives inside it.


Step 2

increment() is returned.

Normally the execution context would be destroyed.

But...


Step 3

JavaScript notices that increment() still references count.

So the engine preserves the variable.

This preserved environment becomes the closure scope.


Step 4

Each time:

code
add();

runs, it accesses the same stored count.


Garbage Collection and Closures

Closures are incredibly useful.

But they affect memory.

JavaScript's garbage collector can only remove closure data when:

code
add = null;

or no references remain.

Until then:

code
count

stays alive.


Real-World Closure Example

Creating personalized greeters:

code
function createGreeter(name) {
  return function() {
    console.log(
      `Hello ${name}`
    );
  };
}

const greetAnik =
  createGreeter("Anik");

greetAnik();

Output:

code
Hello Anik

The closure remembers:

code
name = "Anik"

even though createGreeter() has already finished.


What Are Decorators?

Now let's level up.

A decorator is a function that wraps another function and adds new behavior.

Think of decorators like giving a function superpowers.

The original function remains unchanged.

The decorator simply enhances it.


Your First Decorator

code
function logDecorator(fn) {
  return function(...args) {

    console.log(
      `Calling ${fn.name}`
    );

    return fn(...args);
  };
}

function greet(name) {
  return `Hello ${name}`;
}

const decorated =
  logDecorator(greet);

console.log(
  decorated("Anik")
);

Output:

code
Calling greet
Hello Anik

Pretty neat.


How Decorators Work Internally

Notice something?

code
function logDecorator(fn) {

  return function(...args) {
    return fn(...args);
  };
}

The inner function references:

code
fn

from the outer scope.

That means...

The decorator is actually using a closure.


Decorators Are Built on Closures

This relationship is important:

code
Scope
   ↓
Closure
   ↓
Decorator

Scopes make closures possible.

Closures make decorators possible.

These concepts build on each other.


Practical Decorator: Logging

code
function logger(fn) {

  return function(...args) {

    console.log(
      `Calling ${fn.name}`
    );

    return fn(...args);
  };
}

Useful for:

  • Debugging
  • Monitoring
  • Analytics
  • Observability

Practical Decorator: Timing Functions

code
function timer(fn) {

  return function(...args) {

    const start =
      performance.now();

    const result =
      fn(...args);

    const end =
      performance.now();

    console.log(
      `Took ${end-start}ms`
    );

    return result;
  };
}

Great for performance profiling.


Practical Decorator: Debouncing

One of the most common frontend patterns.

code
function debounce(
  fn,
  delay
) {

  let timer;

  return function(...args) {

    clearTimeout(timer);

    timer = setTimeout(
      () => fn(...args),
      delay
    );
  };
}

Used heavily in:

  • Search boxes
  • Window resize events
  • Scroll handlers

Notice how the closure remembers:

code
timer

between calls.


Common Closure Pitfall

Closures capture references.

Not copies.

Example:

code
for (
  var i = 0;
  i < 3;
  i++
) {
  setTimeout(
    () => console.log(i),
    100
  );
}

Output:

code
3
3
3

Why?

All callbacks reference the same i.


Fix Using Block Scope

code
for (
  let i = 0;
  i < 3;
  i++
) {
  setTimeout(
    () => console.log(i),
    100
  );
}

Output:

code
0
1
2

Each iteration gets its own scope.


Hoisting and Scope

JavaScript hoists declarations.

Example:

code
console.log(a);

var a = 10;

Output:

code
undefined

Internally:

code
var a;

console.log(a);

a = 10;

But:

code
console.log(b);

let b = 10;

Produces:

code
ReferenceError

Because of the Temporal Dead Zone (TDZ).


Putting It All Together

Scope

Determines where variables live.

Closure

Allows functions to remember variables.

Decorator

Uses closures to enhance functions.

These three concepts form the foundation of modern JavaScript architecture.


TL;DR Quick Recap

  • JavaScript uses lexical scope.
  • Variables live inside scopes.
  • Execution contexts manage variables and scope chains.
  • Closures preserve variables after a function finishes.
  • Closures survive until garbage collection removes them.
  • Decorators wrap functions with additional behavior.
  • Decorators are powered by closures.
  • Many frontend patterns rely on closures and decorators.

Frequently Asked Questions

What is scope in JavaScript?

Scope determines where variables are accessible.

JavaScript supports:

  • Global Scope
  • Function Scope
  • Block Scope
  • Module Scope

What is lexical scope?

Lexical scope means variable access is determined by where code is written, not where functions are called.


What is a closure?

A closure is a function that remembers variables from its surrounding scope even after that scope has finished executing.


Why are closures useful?

Closures allow developers to:

  • Preserve state
  • Create private variables
  • Build factories
  • Implement memoization
  • Build decorators

Can closures cause memory leaks?

Yes.

If closures hold references to large objects that are never released, memory usage can grow unnecessarily.


What is an execution context?

An execution context is the environment JavaScript creates when executing code.

It contains:

  • Variables
  • Scope references
  • This binding

What is a decorator in JavaScript?

A decorator is a function that wraps another function or class and extends its behavior.


Are decorators part of JavaScript?

Decorators are commonly used through patterns and are heavily supported in TypeScript and modern frameworks.


How do decorators work?

Decorators return a new function that calls the original function while adding extra functionality.


Why do decorators rely on closures?

The wrapper function must remember the original function being decorated.

Closures make that possible.


What frameworks use decorators?

Examples include:

  • Angular
  • NestJS
  • MobX
  • TypeScript ecosystems

Decorator-like patterns also appear throughout React and Node.js applications.


What is the scope chain?

The scope chain is the sequence of scopes JavaScript searches when resolving variables.

code
Local
 ↓
Outer
 ↓
Global

What is hoisting?

Hoisting is JavaScript's behavior of moving declarations to the top of their scope before execution.


Why does var behave differently from let?

var is function-scoped.

let is block-scoped and respects the Temporal Dead Zone.


Key Takeaways

  • JavaScript uses lexical scope.
  • Execution contexts manage variable environments.
  • Closures preserve variables after execution.
  • Closures enable private state and powerful abstractions.
  • Decorators enhance functions without modifying them.
  • Decorators rely on closures internally.
  • Understanding these concepts is essential for mastering JavaScript.

Final Thoughts

Scopes, closures, and decorators are some of the most important concepts in JavaScript.

At first they may seem like separate topics.

But they're deeply connected.

Scopes create environments.

Closures preserve environments.

Decorators leverage closures to enhance behavior.

Once you truly understand these three concepts, JavaScript starts feeling less like magic and more like a beautifully designed system.

And that's when your code starts leveling up. 🚀


About the Author

Anik Sikder is a Software Engineer specializing in Frontend Development, Backend Systems, Cloud Infrastructure, Software Architecture, and Modern Web Technologies.

He writes about JavaScript, TypeScript, Python, Django, FastAPI, distributed systems, DevOps, and scalable software engineering practices.

$ tags

javascriptscopesclosuresdecoratorslexical-scopeexecution-contextfrontendweb-development

$ ls related_articles

status: end_of_file