Prototype Chain
The prototype chain is the series of linked objects JavaScript walks up when looking for a property or method, inheriting from each object's prototype until it finds the property or reaches the end of the chain.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples (like fetch to external APIs) may have limitations. For the best experience:
- Download Node.js to run JavaScript on your computer
- Use your browser's Developer Console (Press F12) to test code snippets
- Create a .html file with <script> tags and open it in your browser
The prototype system is the hidden engine behind JavaScript's object model. Unlike classical languages such as Java, C#, or C++, JavaScript doesn't have traditional "classes" under the hood — even ES6 classes are syntactic sugar layered on top of its prototype chain. Understanding this chain is essential if you want to build high-performance data models, reusable components, advanced game logic, or frameworks.
This is exactly where senior-level JavaScript engineers separate from beginners: they don't treat OOP as a set of keywords, but as mastery of how memory, delegation, inheritance, method lookup, and object links actually work in the engine.
🔥 The Real Backbone: [[Prototype]] and Delegation
Every JavaScript object carries an internal link to another object called its [[Prototype]] . When you access a property on an object and it doesn't exist, the engine looks up the chain until it finds the property or hits null . This is called delegation , and it creates the illusion of inheritance.
Unlike classical inheritance where objects copy methods, JavaScript objects share them via delegation. This reduces memory usage and increases performance — which is why frameworks like React, Vue internals, Node streams, game engines, and custom interpreters often rely heavily on prototype structures.
🔥 Constructor Functions: The Original "Class" Pattern
Before ES6 introduced the class keyword, constructor functions were the core of JS OOP. They still work exactly the same under the hood:
- • Player.prototype is where shared methods live
- • Instances don't have .attack — they access it through the prototype chain
A common beginner mistake is adding methods inside the constructor:
🔥 ES6 Classes Are Just Syntax Sugar
This is identical to constructor+prototype under the hood. But ES6 classes introduce several advanced behaviours:
- ✔ Methods are non-enumerable (cleaner iteration)
- ✔ Strict mode is automatically enabled
- ✔ Cannot be called without new
- ✔ Static methods attach to the constructor, not the prototype
- ✔ Private fields ( #hp ) live on the instance, not the prototype
Private fields are not part of the prototype chain. They live directly inside each instance, making them secure and non-exposed.
🔥 Deep Prototype Chain Resolution & Performance
- Does player have attack ?
- If not → check player.__proto__ (same as Player.prototype )
- If not → check the next prototype
- Continue until null
Try not to exceed 2–3 prototype levels for performance-critical systems such as games, UI frameworks, custom renderers, physics engines, audio engines, animation systems, and interpreters.
🔥 Composition Over Inheritance (Modern Best Practice)
In advanced engineering, the best pattern is "objects with capabilities," not long class trees.
Composed objects scale 10× better in fast-growing systems.
🔥 Prototype Manipulation & Live Extension
You can dynamically add behaviour to prototypes — used heavily in polyfills & frameworks:
Powerful, but dangerous. If you add something wrong, every array in your entire project breaks. Modern best practice: Never modify built-ins except for polyfills.
🔥 Advanced Pattern: Linked Prototypes for Shared Config
Great for games, AI models, or user-settings architecture:
You only override what's different. This reduces memory footprint for thousands of game entities.
🔥 Prototype Shadowing & Hidden Mistakes
One of the most confusing problems is property shadowing — when a child object defines a property with the same name as its prototype.
Shadowing can break logic in shared stats, shared configuration, shared caches, and inheritance-based method overrides.
🔥 Advanced OOP Pattern: Mixins
Mixins let you add capabilities without deep inheritance. Modern, safe implementation:
This works perfectly for game abilities, AI behaviours, utilities, and UI widgets.
🔥 Private Data Using Closures
Closures give stronger security than ES6 #private fields:
- Game currencies
- Credit balances
- Internal counters
- Preventing tampering
🔥 Prototype Pollution — Security Risk
If you're building large systems, watch for prototype pollution:
This vulnerability can break your entire system.
🔥 Detecting Prototype Issues & Debugging
🔥 Understanding Object.create(null)
Used in: Redux, compilers, renderers, interpreters, AI tokenizers, and JS engines internally.
🔥 Factory Functions vs Classes vs Prototypes
Factory Functions
- Best for configuration-heavy objects
- Perfect for game items, user settings, custom AI nodes
- Easy to test
- No "this" confusion
- Natural closures for private data
Classes
- Best for entities with identity
- Good for UI components
- Good for data models
Prototypes
- Best for lightweight shared behaviour
- Perfect for performance-critical code
- Ideal for objects created in massive numbers (e.g., 10,000 NPCs)
🎮 Real Example: Designing a Game Entity System
This is how real game studios structure their engines.
🔥 Professional-Level OOP Architecture
Best Practices for Scalable Apps:
- ✔ Prefer composition for features
- ✔ Use classes only for true entities
- ✔ Keep prototypes shallow (2-3 levels max)
- ✔ Use factory functions for configurable objects
- ✔ Avoid complex inheritance hierarchies
- ✔ Avoid class methods that mutate global/shared state
- ✔ Keep prototypes clean and predictable
- ✔ Never shadow inherited properties accidentally
- ✔ Use static methods for utilities
- ✔ Use closures for private state when needed
⚡ Performance Tips
- • Keep prototype chains shallow
- • Use composition over deep inheritance
- • Define methods on prototypes, not in constructors
- • Use Object.create(null) for dictionaries
- • Freeze prototypes after definition
- • Modify prototypes after objects are created
- • Create deep inheritance hierarchies (>3 levels)
- • Define methods inside constructors
- • Modify built-in prototypes (except polyfills)
- • Accidentally shadow prototype properties
🎯 Key Takeaways
- ✓ JavaScript uses delegation, not classical inheritance
- ✓ ES6 classes are prototype sugar with extra features
- ✓ Avoid putting functions inside constructors
- ✓ Composition > inheritance for modern architecture
- ✓ Dynamic prototypes can be useful but risky
- ✓ Deep prototype chains hurt performance
- ✓ Use objects + capabilities for flexible behaviour
- ✓ Keep data on instances, behaviour on prototypes
- ✓ Watch for prototype pollution in user inputs
- ✓ Use closures for truly private data
To master JavaScript's OOP at a senior-engineer level, you must understand not just what prototypes are, but how the engine uses them to resolve property lookups, share behavior, and structure memory internally. The prototype chain is at the core of every object you've ever created, from arrays and promises to DOM nodes, async functions, classes, and even built-in browser APIs. These patterns make code easier to maintain and help build scalable, high-performance applications.
Practice quiz
What is the internal link every object uses to look up missing properties called?
Each object has an internal [[Prototype]] link; the engine walks it to resolve properties via delegation.
In the lesson, ES6 classes are described as...
- Syntactic sugar over the prototype chain
- A completely new object model
- Faster than prototypes always
- Unable to use inheritance
Answer: Syntactic sugar over the prototype chain. ES6 classes are syntax sugar layered on top of JavaScript's prototype system.
Why is adding a method inside the constructor (this.attack = () => {}) called a mistake?
- It is a syntax error
- Constructors cannot hold functions
- It runs too slowly to call
- It creates a new function per instance, wasting memory
Answer: It creates a new function per instance, wasting memory. Defining methods in the constructor creates a separate copy per instance instead of sharing one on the prototype.
For const child = Object.create(parent), where parent.greet exists, calling child.greet()...
- Throws because child has no greet
- Is delegated up to parent.greet()
- Returns undefined
- Copies greet onto child
Answer: Is delegated up to parent.greet(). child delegates the lookup up the prototype chain to parent.greet().
After player.score = 300 shadows base.score = 100 (player created via Object.create(base)), what is base.score?
- 100
- 300
- 0
- undefined
Answer: 100. Assigning to player.score shadows but does not change base.score, which stays 100.
What does new D().constructor.name return for class A{}, B extends A, C extends B, D extends C?
- "A"
- "Object"
- "D"
- "C"
Answer: "D". The constructor name of a D instance is "D".
The modern best practice the lesson favors over deep inheritance is...
- Global variables
- Composition (objects with capabilities)
- Copying all methods
- Avoiding functions
Answer: Composition (objects with capabilities). Composition (mixing in capabilities) scales better than long inheritance chains.
For performance-critical systems, the lesson says to keep prototype chains...
- As deep as possible
- Exactly 10 levels
- Empty
- Shallow, about 2-3 levels max
Answer: Shallow, about 2-3 levels max. Deep chains slow lookups; keep them to 2-3 levels for hot code.
What does Object.create(null) produce?
- A frozen array
- An object with no prototype (a pure dictionary)
- A copy of Object.prototype
- null
Answer: An object with no prototype (a pure dictionary). Object.create(null) makes an object with no prototype, avoiding inherited keys like toString.
Prototype pollution is a security risk that arises when you...
- Use too many classes
- Freeze a prototype
- Let untrusted input write to __proto__
- Call Object.create
Answer: Let untrusted input write to __proto__. Untrusted input reaching __proto__ can pollute the shared prototype and affect every object.
Continue this course
- Previous: JavaScript Modules
- Next: Functional Programming