Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
JavaScriptReactJSFrontend DevelopmentWeb DevelopmentES6

JavaScript Prototypes

Understand how JavaScript inheritance really works — from object literals and prototype chains to constructor functions, Object.create(), and ES6 classes as syntactic sugar.

Mar 20, 20268 min read
JavaScript Prototypes

Introduction

JavaScript is often described as a prototype-based language. Unlike classical inheritance in languages like Java or C++, JavaScript objects inherit behavior by linking to other objects through an internal prototype reference.

If you have used ES6 class syntax and assumed that is how inheritance works under the hood, this guide will change how you read JavaScript code. Classes are useful — but prototypes are the foundation.

This article walks through the full mental model:

  1. Core fundamentals — objects, functions, and prototype-based design
  2. Object creation issues — why manual literals do not scale
  3. The prototype concept — sharing methods and Object.create()
  4. Constructor functions & new — automated object creation
  5. The prototype chain — how property lookup works
  6. Modern JavaScript (ES6) — classes as syntactic sugar

Each section includes practical examples you can run in the browser console or Node.js.

Core Fundamentals

Before prototypes click, you need three foundational ideas clear in your mind.

Prototype-based language

JavaScript does not copy class blueprints into instances. Instead, each object can delegate property access to another object called its prototype. When a property is missing on the object itself, the engine walks the chain until it finds a match or reaches the end.

Everything is an object (almost)

Primitives (string, number, boolean, null, undefined, symbol, bigint) are not objects — but JavaScript wraps them when you access methods:

javascript
const name = 'Kazi'
name.toUpperCase() // 'KAZI' — temporary String object under the hood

Arrays, functions, dates, and plain objects are all objects with internal prototype links.

Functions are objects too

Functions can hold properties and methods just like any other object:

javascript
function greet(name) {
  return `Hello, ${name}`
}
 
greet.label = 'Greeter'
greet.version = 1
 
console.log(greet.label) // 'Greeter'
console.log(greet('Kazi')) // 'Hello, Kazi'

Because functions are objects, they can also participate in the prototype system — which becomes essential when you use constructor functions.

Object Creation Issues

The simplest way to create an object is an object literal:

javascript
const user1 = {
  name: 'Kazi',
  role: 'Developer',
  greet() {
    return `Hi, I'm ${this.name}`
  },
}
 
const user2 = {
  name: 'Alex',
  role: 'Designer',
  greet() {
    return `Hi, I'm ${this.name}`
  },
}

This works for one or two objects. It breaks down quickly at scale.

Manual object literals

Every new user requires copy-pasting the same structure. There is no single source of truth for shared behavior.

Code duplication

If greet changes, you must update it in every literal. Miss one and you get inconsistent behavior across instances.

Memory inefficiency

Each object carries its own copy of greet. With thousands of instances, the same function body is stored repeatedly in memory.

Performance impact

More duplicated functions mean more memory pressure and slower garbage collection — especially in large client-side apps or long-running Node.js servers.

Performance

Prototypes solve this by storing shared methods once on a parent object that all instances link to — reducing memory pressure and GC work at scale.


The Prototype Concept

A prototype is the object an instance delegates to when it does not own a property locally.

Sharing methods and properties

Instead of attaching greet to every user, define it once on a shared parent:

javascript
const userPrototype = {
  greet() {
    return `Hi, I'm ${this.name}`
  },
}
 
const user1 = Object.create(userPrototype)
user1.name = 'Kazi'
user1.role = 'Developer'
 
const user2 = Object.create(userPrototype)
user2.name = 'Alex'
user2.role = 'Designer'
 
console.log(user1.greet()) // "Hi, I'm Kazi"
console.log(user2.greet()) // "Hi, I'm Alex"

Both user1 and user2 share the same greet function in memory.

Object.create(parent)

Object.create(proto) creates a new object whose internal [[Prototype]] points to proto:

javascript
const animal = {
  eats: true,
  describe() {
    return this.name
  },
}
 
const dog = Object.create(animal)
dog.name = 'Rex'
dog.barks = true
 
console.log(dog.eats) // true — inherited from animal
console.log(dog.describe()) // 'Rex'

Parent-child inheritance

The child object (dog) inherits from the parent (animal). The parent does not know about the child — inheritance is a one-way lookup relationship.

Common method storage

Shared logic lives on the prototype. Instance-specific data (name, role, barks) lives on the object itself:

plaintext
user1 ──► userPrototype ──► Object.prototype ──► null
  │              │
 name          greet()
 role

Constructor Functions & new

Constructor functions automate object creation and wire up the prototype link for you.

Automated object creation

A constructor is a regular function intended to be called with new:

javascript
function User(name, role) {
  this.name = name
  this.role = role
}
 
User.prototype.greet = function () {
  return `Hi, I'm ${this.name}`
}
 
const user1 = new User('Kazi', 'Developer')
const user2 = new User('Alex', 'Designer')
 
console.log(user1.greet()) // "Hi, I'm Kazi"
console.log(user2.greet()) // "Hi, I'm Alex"

The prototype property

Every function has a prototype property. When you call new User(), JavaScript:

  1. Creates a new empty object
  2. Sets the object's [[Prototype]] to User.prototype
  3. Binds this to the new object inside the function
  4. Returns the new object (unless the function returns its own object)
javascript
console.log(user1.__proto__ === User.prototype) // true (legacy accessor)
console.log(Object.getPrototypeOf(user1) === User.prototype) // true (preferred)

Tip

Prefer Object.getPrototypeOf() over __proto__ — the legacy accessor is deprecated and may be removed from future JavaScript engines.

Internal linkage

The instance does not store greet directly. It delegates to User.prototype:

javascript
console.log(user1.hasOwnProperty('name')) // true
console.log(user1.hasOwnProperty('greet')) // false
console.log('greet' in user1) // true — found on prototype

Reference pointing

All instances created from the same constructor share one prototype object:

javascript
User.prototype.team = 'Engineering'
 
console.log(user1.team) // 'Engineering'
console.log(user2.team) // 'Engineering' — same reference

Warning

Mutating Constructor.prototype affects every existing instance immediately. Treat prototype properties as shared defaults, not per-user state.


Prototype Chain

When you access user1.greet, JavaScript does not stop at user1. It walks the prototype chain until it finds the property or hits null.

Property lookup mechanism

javascript
function Animal(name) {
  this.name = name
}
 
Animal.prototype.eat = function () {
  return `${this.name} is eating`
}
 
function Dog(name, breed) {
  Animal.call(this, name)
  this.breed = breed
}
 
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
 
Dog.prototype.bark = function () {
  return `${this.name} says woof`
}
 
const rex = new Dog('Rex', 'Labrador')
 
console.log(rex.bark()) // 'Rex says woof'
console.log(rex.eat()) // 'Rex is eating' — found on Animal.prototype

Member access hierarchy

Lookup order for rex.eat:

plaintext
rex
 └── Dog.prototype
      └── Animal.prototype
           └── Object.prototype
                └── null

Inheritance structure

Object.prototype is the root of almost all object chains. It provides built-in methods like toString, hasOwnProperty, and valueOf:

javascript
console.log(rex.toString()) // '[object Object]'

If a property is not found anywhere in the chain, the result is undefined (or a ReferenceError for undeclared variables).

javascript
console.log(rex.fly) // undefined — no fly on rex or any prototype

Modern JavaScript (ES6)

ES6 introduced the class keyword. It looks like classical inheritance — but it is syntactic sugar over the prototype system.

JavaScript classes

javascript
class User {
  constructor(name, role) {
    this.name = name
    this.role = role
  }
 
  greet() {
    return `Hi, I'm ${this.name}`
  }
 
  static createGuest() {
    return new User('Guest', 'Visitor')
  }
}
 
const user = new User('Kazi', 'Developer')
console.log(user.greet()) // "Hi, I'm Kazi"

Syntactic sugar

This class is equivalent to the constructor + prototype pattern:

javascript
// Roughly what the engine does under the hood
function User(name, role) {
  this.name = name
  this.role = role
}
 
User.prototype.greet = function () {
  return `Hi, I'm ${this.name}`
}

You can verify the prototypal nature:

javascript
console.log(typeof User) // 'function'
console.log(User.prototype.greet === user.greet) // false (bound differently)
console.log(user instanceof User) // true
console.log(Object.getPrototypeOf(user) === User.prototype) // true

Class inheritance still uses prototypes

extends sets up the prototype chain between constructor prototypes:

javascript
class Animal {
  constructor(name) {
    this.name = name
  }
 
  eat() {
    return `${this.name} is eating`
  }
}
 
class Dog extends Animal {
  constructor(name, breed) {
    super(name)
    this.breed = breed
  }
 
  bark() {
    return `${this.name} says woof`
  }
}
 
const rex = new Dog('Rex', 'Labrador')
console.log(rex.bark()) // 'Rex says woof'
console.log(rex.eat()) // 'Rex is eating'

super() calls the parent constructor. Method lookup still walks the same prototype chain — Dog.prototype links to Animal.prototype.

Underlying prototypal nature

Whether you write class, constructor functions, or Object.create(), the mental model is the same:

ApproachShared methods live onInstance links via
Object literalNowhere (duplicated)N/A
Object.create()Parent object[[Prototype]]
Constructor + newConstructor.prototype[[Prototype]]
ES6 classClass prototype methods[[Prototype]]

Conclusion

JavaScript prototypes are not a legacy footnote — they are the core inheritance mechanism of the language. ES6 classes make the syntax familiar, but debugging, reading framework source code, and understanding performance all benefit from knowing the prototype chain.

Key takeaways:

  • Avoid duplicating methods across object literals — use a shared prototype
  • Object.create() gives you explicit control over the parent object
  • Constructor functions + new automate instance creation and prototype wiring
  • Property lookup walks the chain: instance → prototype → … → Object.prototype → null
  • class is syntactic sugar — prototypes still power inheritance under the hood

The next time you see instanceof, Object.getPrototypeOf(), or wonder why a method appears on an object it was never assigned to — trace the prototype chain. That is where the answer lives.


Have questions about prototypes or inheritance patterns? Reach out via the contact page.

Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime