Closures, prototypes, and classes are fundamental JavaScript concepts that help developers manage scope, reuse code, and build scalable applications.
Closures allow functions to remember and access variables from their outer scope even after execution, enabling data encapsulation and state management.
Prototypes form the backbone of JavaScript’s object-oriented behavior by allowing objects to share properties and methods efficiently.
Classes provide a cleaner and more structured syntax over prototypes, supporting features like inheritance and the use of super to access parent class constructors and methods.
JavaScript Closures
Closures occur when a nested function accesses variables from its outer (parent) function even after the outer function finishes executing.
This creates private data and persistent state, solving common problems like counter bugs and module patterns.
Closures are everywhere—from event listeners to API wrappers—making your code memory-efficient and secure.
How Closures Work
A closure "closes over" outer variables, bundling them with the inner function. The outer function's scope lives as long as the inner function exists.
Core Mechanism

Simple Example
function createCounter() {
let count = 0; // Private variable
return function() {
count++; // Closure accesses 'count'
return count;
};
}
const myCounter = createCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2Here, count stays alive, preventing global pollution. Without closure, count would reset.
Practical Use Cases
Closures shine in real apps:
1. Event Handlers: Preserve this context in loops.
2. Private Methods: Hide implementation details.
3. Memoization: Cache expensive calculations.
Loop Pitfall Fix:
// ❌ Wrong - all buttons show "Button 4"
for (let i = 0; i < 4; i++) {
button[i].onclick = () => alert(i); // Closure captures changing i
}
// ✅ Correct with closure
for (let i = 0; i < 4; i++) {
button[i].onclick = (function(index) {
return () => alert(index);
})(i);
}Pro Tip: Use const for outer variables to prevent accidental reassignment.
JavaScript Prototypes
Prototypes form JavaScript's inheritance system—a chain where objects delegate missing properties to "parent" prototypes.
Every object has a hidden [[Prototype]] link, enabling shared methods without copying code.
This underpins efficient memory use in large apps and explains instanceof.
Prototype Chain Basics
All objects inherit from Object.prototype. When you access obj.method(), JS walks the chain until found or undefined.
Key Concepts:
1. __proto__ (deprecated): Direct prototype link.
2. Object.getPrototypeOf(obj): Modern accessor.
3. Object.create(proto): Creates object with custom prototype.
Visual Chain
myCar → Car.prototype → Object.prototype → nullExample
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.start = function() {
return `${this.make} ${this.model} started!`;
};
const tesla = new Car('Tesla', 'Model 3');
console.log(tesla.start()); // Inherited methodModifying Prototypes Live
// Affects ALL instances
Car.prototype.honk = function() { return 'Beep!'; };
tesla.honk(); // Works immediatelyBest Practice: Define methods on prototype, not inside constructor.
ES6 Classes and Inheritance
ES6 classes provide familiar class syntax atop prototypes, simplifying OOP with constructor, super(), and extends.
They're not "real" classes (still prototypal) but make code readable for teams. Perfect for React components or game objects.
Class Basics and 'super'
Classes centralize initialization and methods.
Syntax
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
return `${super.speak()} Woof!`; // Call parent method
}
}Rules
1. Always call super() first in subclass constructor.
2. Use super.method() to extend parent behavior.
3. extends sets up prototype chain.
Inheritance Deep Dive
Multiple Levels:
class Puppy extends Dog {
constructor(name, breed, age) {
super(name, breed);
this.age = age;
}
play() {
return `${this.name} is playing!`;
}
}Class Fields (Modern)
class Counter {
count = 0; // Instance field
increment() {
this.count++;
}
}Real-World Integration Example
Combine all three in a Task Manager:
function createTaskManager() {
let tasks = []; // Closure: private data
class Task {
constructor(title, priority) {
this.title = title;
this.priority = priority;
this.completed = false;
}
toggle() {
this.completed = !this.completed;
}
}
Task.prototype.getStatus = function() {
return this.completed ? 'Done' : 'Pending';
};
return {
add(title, priority) {
const task = new Task(title, priority);
tasks.push(task);
},
getTasks() {
return tasks.map(t => `${t.title} [${t.getStatus()}]`);
}
};
}
const manager = createTaskManager();
manager.add('Learn closures', 'High');
console.log(manager.getTasks());This uses closure (private tasks), prototypes (shared getStatus), and classes (structured objects).
Memory Note: Closures retain outer scope—use weakMap for large datasets to prevent leaks.