Most prototype bugs come from a handful of repeated mistakes.
1. Putting a mutable object on the prototype
Everything on a prototype is shared between instances. That is fine for methods. It is dangerous for arrays and objects you plan to change.
function Team() {}
Team.prototype.members = []; // shared by EVERY team!
const a = new Team();
const b = new Team();
a.members.push("Maya");
console.log(b.members); // → ["Maya"] (b sees it too)
Create per-instance data in the constructor instead:
function Team() { this.members = []; }
const a = new Team();
const b = new Team();
a.members.push("Maya");
console.log(b.members); // → []
Class fields (members = [];) are per instance, so they avoid this trap.
2. Changing built-in prototypes
Array.prototype.last = function () { return this[this.length - 1]; };
It works, but it changes the language for everyone in the page, including libraries. If a future version of JavaScript adds a method with the same name but different behaviour, your code can break things, and for...in over arrays now sees last. Do not add to built-in prototypes. Write a function instead: last(array). (Polyfills that implement an official method exactly as specified are the careful exception.)
3. for…in picks up inherited properties
const parent = { inherited: true };
const child = Object.create(parent);
child.own = 1;
const keys = [];
for (const key in child) keys.push(key);
console.log(keys); // → ["own", "inherited"]
Use Object.keys/Object.entries, or check Object.hasOwn(child, key).
4. Replacing a prototype and losing the constructor
function Dog() {}
Dog.prototype = { bark() { return "woof"; } }; // replaced the whole object
console.log(new Dog().constructor === Dog); // → false (it is now Object)
Either add methods one by one, or restore it: Dog.prototype.constructor = Dog. Classes do this correctly for you.
5. Forgetting new
Calling a constructor function without new does not create an object. Use a class (which throws a clear error), or guard with new.target.
6. Losing this when passing methods
class Button {
label = "OK";
click() { return this.label; }
}
const b = new Button();
const handler = b.click;
try {
handler();
} catch (error) {
console.log(error.name); // → TypeError (this is undefined in a class method called alone)
}
Fix with .bind(b), an arrow function wrapper, or define the method as an arrow-function field. See this in JavaScript.
7. Checking types with constructor.name
obj.constructor.name can be changed or lost (see mistake 4), and minifiers rename classes. Use instanceof, Array.isArray(), or check for the features you need.
8. Confusing prototype and __proto__
Function.prototype(the property on constructor functions and classes): what instances will use.Object.getPrototypeOf(obj): the prototype of this object right now.
class A {}
const a = new A();
console.log(Object.getPrototypeOf(a) === A.prototype); // → true
console.log(Object.getPrototypeOf(A) === Function.prototype); // → true (A itself is a function)
9. Deep inheritance trees
Long chains are hard to follow and slow to debug. Prefer shallow structures and composition.
Safe defaults
Object.keys for looping, instanceof for checks, and never modify built-in prototypes.