Skip to main content
Master two fundamental JavaScript features: closures that capture lexical scope and prototype chains that enable object-oriented programming without classes.

Overview

Closures and prototypes are essential to JavaScript’s identity as a language. Closures enable powerful functional programming patterns, while prototypes provide a flexible inheritance mechanism.
Understanding closures and prototypes deeply is crucial for implementing a JavaScript engine and writing efficient JavaScript code.

Closure implementation with environment chains

Closures allow functions to access variables from their lexical scope even after the outer function has returned. This is implemented using environment chains.

What is a closure?

A closure is a function bundled together with references to its surrounding state (lexical environment).

Environment chains

Environments form a chain from the innermost scope to the global scope:
1

Create environment

When a function is called, create a new environment containing its local variables and parameters.
2

Link to parent

Set the environment’s parent to the lexical parent scope (not the dynamic caller).
3

Variable lookup

Search for variables by traversing the chain from inner to outer environments.
4

Capture references

Functions store a reference to their creation environment, not a copy.

Implementation

Environments must use shared pointers or garbage collection to prevent memory leaks. Circular references between closures can cause memory issues.

Upvalues optimization

To avoid keeping entire environment chains alive, use upvalues to capture only needed variables:
Lua pioneered the upvalue technique, which is more memory-efficient than capturing full environments.

Prototype chain traversal

JavaScript uses prototype-based inheritance instead of class-based inheritance. Every object has a prototype, and property lookups traverse the prototype chain.

Prototype basics

Prototype chain structure

Every object contains:
  • Own properties (stored directly on the object)
  • A hidden [[Prototype]] link to another object

Implementation

Property lookup optimization

Prototype chain traversal can be slow. Real JavaScript engines use sophisticated optimizations.

Inline caching

Cache property locations to avoid repeated lookups:
V8’s “hidden classes” and SpiderMonkey’s “shapes” use this technique to make property access as fast as C struct field access.

Polymorphic inline caching

Handle multiple object shapes at the same call site:
Single object shape observed:
Writing properties in different orders creates different shapes, leading to megamorphic inline caches and slower performance.

Best practices

Minimize closure size

Only capture variables actually used by the closure to reduce memory usage

Avoid modifying prototypes

Changing prototypes at runtime invalidates inline caches and hurts performance

Consistent object shapes

Initialize objects with all properties in the same order for better optimization

Prefer composition

Use composition over deep inheritance chains for better performance and maintainability

Common pitfalls

Closures keep their entire environment alive, which can prevent garbage collection:
Modifying Object.prototype affects all objects:
Own properties shadow prototype properties:

Next steps

Parsing and AST

Learn about lexical analysis, tokenization, and AST construction

Bytecode and VM

Design bytecode and implement a stack-based virtual machine