Object.create & null-prototype objects
Create objects with a chosen prototype, inspect and change prototype links carefully, and use null-prototype dictionaries without inherited surprises.
- 01Create with a chosen prototypeUse
Object.create(proto, descriptors)and verify the link. - 02Pick dictionary storage safelyCompare
{},Object.create(null), andMapfor user keys. - 03Keep prototype links stableExplain why
setPrototypeOfis a repair tool, not a hot-path habit.
Choose the prototype link up front
The previous prototype lessons showed that an object can inherit from another object. This lesson is about choosing that link deliberately. Object.create(proto) creates a new object whose prototype is exactly proto. No constructor call is required, and no properties are copied from the prototype into the new object.
Think of a prototype as the warehouse a shop checks when a shelf is empty. Object.create(base) is building a new shop and choosing its warehouse at construction time. Object.setPrototypeOf(shop, otherBase) is switching an existing shop to a different warehouse while customers are already walking around: possible, but disruptive and slow enough that engines and MDN warn against making it a normal habit.
Use Object.create(proto) when you want a new object with a chosen prototype, use Object.create(null) when you want no inherited properties at all, and avoid changing prototypes after creation unless you are repairing or adapting unusual objects.
You can stock a shop with its own items, but it can also rely on a warehouse. If a shelf has no umbrellas, the shop asks the warehouse. A JavaScript object does the same kind of lookup: own property first, prototype next, then the prototype’s prototype, until the chain ends.
- In real life: A new shop picks a warehouse before opening
- In JavaScript:
Object.create(proto)chooses the prototype at creation - In real life: A missing item is fetched from the warehouse
- In JavaScript: A missing property is looked up on the prototype
- In real life: Switching warehouses mid-day disrupts staff
- In JavaScript:
Object.setPrototypeOfcan disrupt engine optimizations
Where the analogy stops: A real shop moves boxes around. JavaScript prototype lookup does not copy properties; it follows a link when a property is missing.
You will also meet the opposite case: a shop with no warehouse at all. A null-prototype object is a bare dictionary with no inherited surprises like toString, constructor, or hasOwnProperty. That connects directly to Map & Set, Choosing a data structure, and JSON.
Object.create: build with a chosen prototype
STEP THROUGHObject.create takes a prototype object, or null. It returns a new object whose internal [[Prototype]] link points there. The optional second argument uses the same property descriptor format as Object.defineProperties: each property can define value, writable, enumerable, and configurable.
The defaults matter. In a descriptor, writable, enumerable, and configurable default to false. The example deliberately sets enumerable: true so name shows up in Object.keys, but it leaves writable off at first. In strict code, assigning to that property throws a TypeError; in an old sloppy script it silently fails.
Predict the inherited method, the prototype checks, and whether line 16 can change name. Then step through the recorded run.
script
"use strict"; greet() { return "hi from base"; },}; const child = Object.create(base, { name: { value: "Kid", enumerable: true },}); console.log(child.greet());console.log(Object.getPrototypeOf(child) === base);console.log(base.isPrototypeOf(child));try { child.name = "Ada";} catch (error) { console.log(error.name);}console.log(child.name);Line 8 is the important moment: the new object is born already linked to base. Line 12 then proves no method was copied. child cannot find greet on itself, so lookup climbs to base. Lines 13 and 14 show the two common relationship checks: Object.getPrototypeOf(child) === base from the child side, and base.isPrototypeOf(child) from the ancestor side.
__proto__In an object literal, { __proto__: proto, own: 1 } is special syntax that sets the prototype while creating the object. That is different from assigning obj["__proto__"] later, which goes through a legacy accessor on ordinary objects.
getPrototypeOf & setPrototypeOf
INTERACTIVEObject.getPrototypeOf(obj) is the standard way to inspect an object’s prototype link. Object.setPrototypeOf(obj, proto) changes that link after the object already exists. Reflect.setPrototypeOf(obj, proto) performs the same operation but returns a boolean instead of returning the object or throwing for ordinary failure.
There are limits. You can set a prototype to null to end the chain. You cannot create a cycle: Object.setPrototypeOf(a, a) throws TypeError: Cyclic __proto__ value in the runtime used by these tests. Some objects have immutable prototype rules; for example, changing the prototype of a frozen ordinary object via Reflect.setPrototypeOf reports false.
const toolbox = { label: "toolbox", find() { return "from toolbox"; } };const archive = { label: "archive", find() { return "from archive"; } };const item = { name: "receipt" }; console.log(item.find?.());Object.setPrototypeOf(item, toolbox);console.log(item.find());console.log(Object.getPrototypeOf(item).label);console.log(Reflect.setPrototypeOf(Object.freeze({}), archive));try { Object.setPrototypeOf(item, item);} catch (error) { console.log(error.name + ": " + error.message);}Press Run to execute the fixed example.
Read the source first. This playground changes one object's prototype after creation so you can see the API, then the article explains why stable links are preferred.
Imagine a shop switching from one warehouse system to another during lunch rush. It can be done for an emergency, but you would not design everyday work that way. That is how to treat setPrototypeOf: know it, but prefer stable creation patterns.
- In real life: A shop already has labels, routes, and staff habits
- In JavaScript: An object already has a shape and optimized lookup assumptions
- In real life: Changing warehouses forces everyone to re-check procedures
- In JavaScript: Changing
[[Prototype]]can invalidate optimized property access - In real life: Choosing the warehouse before opening is calm
- In JavaScript: Classes, constructors, and
Object.createset links at creation
Where the analogy stops: Engines are not literally confused workers. The point is that stable structure is easier to optimize than structure that changes underneath running code.
Object.create(null) dictionaries
INTERACTIVEA normal object inherits from Object.prototype. That is usually helpful: it means methods such as toString and hasOwnProperty are available. But if you are using an object as a dictionary for user-supplied keys, inherited names can be surprising. Before you set anything, "toString" in {} is already true, and reading ({}).toString gives a function.
Object.create(null) creates an object with no prototype. It has no inherited constructor, no inherited toString, and no inherited hasOwnProperty. Keys such as "constructor" and "__proto__" are just keys. JSON.stringify still works because JSON reads own enumerable properties; it does not require Object.prototype.
const keys = ["toString", "__proto__", "constructor", "hasOwnProperty", "apple"]; const plain = {};const dict = Object.create(null);const map = new Map(); for (const key of keys) { const value = key === "__proto__" ? { polluted: true } : "value:" + key; plain[key] = value; dict[key] = value; map.set(key, value);} console.log(typeof ({}).toString);console.log(plain.polluted);console.log(Object.hasOwn(plain, "__proto__"));console.log(dict["__proto__"]);console.log(typeof Object.create(null).hasOwnProperty);console.log(JSON.stringify(dict));prototype changed; polluted=true__proto__?false[object Object]hasOwnPropertyundefinedvalue:__proto__5Before setting anything, ({}).toString is a function. JSON still works for the null-prototype dictionary: {"toString":"value:toString","__proto__":{"polluted":true},"constructor":"value:constructor","hasOwnProperty":"value:hasOwnProperty","apple":"value:apple"}
On {}, assigning __proto__ uses the inherited accessor and changes this object's prototype. On a null-prototype object and a Map, it is just data.
| Choice | Best for | Watch out for |
|---|---|---|
{} plain object | Known record-like fields such as user.name or options.theme | Inherited names exist; __proto__ assignment has legacy behavior |
Object.create(null) | String-key dictionaries with untrusted or open-ended keys | No methods: use Object.hasOwn(dict, key) and Object.entries(dict) |
Map | Frequent add/delete, any key type, clear collection semantics | Not JSON by default; convert with Array.from(map) or entries when needed |
The Choosing a data structure lesson noted that Object.groupBy returns a null-prototype object. That is not an accident: group names come from data, and data might include "constructor". The Stage 9 Prototype pollution lesson goes deeper into the security angle; for now, remember the title and the habit: do not let untrusted keys accidentally become inherited behavior.
Prototype performance: stable links win
This lesson will not invent benchmark numbers. Modern engines are complicated, and tiny benchmarks often measure warm-up, caching, and optimizer choices more than your real application. The honest guidance is still clear: MDN and engine teams document changing an object’s prototype after creation with Object.setPrototypeOf or __proto__ assignment as slow and deoptimizing. Deep prototype chains also cost lookups because each missing property can require another link to be checked.
- Choose the prototype at creation time with a class, constructor, object literal
__proto__, orObject.create. - Keep the chain shallow and predictable for objects used in hot code.
- Do not use
setPrototypeOfas a normal state-management tool. - Measure real user flows before optimizing; engine-level details come later in Hidden classes & object shapes and Prototypes inside the engine.
- A fixed options bag with trusted keys like
themeandpageSize - A word-count table whose keys come from article text
- Cache data by DOM element objects
- A list of user selections you will add, delete, and iterate often
- A JSON response object with known fields from your API
- A result shaped like
Object.groupBy(items, callback)
Sort each scenario by the best default data structure. There are trade-offs, so read the explanations.
Where you’ll use this
Most everyday application code uses object literals, classes, and Map. You reach for Object.create when the prototype link itself is the point: delegation objects, test doubles that inherit default behavior, small framework internals, and dictionaries with no inherited keys.
function countWords(words) { const counts = Object.create(null); for (const word of words) { counts[word] = (counts[word] || 0) + 1; } return counts;}console.log(countWords(["constructor", "constructor"]).constructor);The word "constructor" is the classic bug. In a plain object, counts.constructor starts as an inherited function, so (counts[word] || 0) + 1 can go wrong in strange ways. A null-prototype dictionary starts empty in the strongest sense: no inherited names, only the keys you add.
The next lesson, Delegation vs classical inheritance, will tease this idea further with OLOO: objects linked to other objects using Object.create, without pretending that JavaScript copies behavior down a class hierarchy.
Common misconceptions
“Object.create copies the prototype’s methods.”
No. It creates a link. If the child cannot find a property on itself, lookup follows the link.
“A null-prototype object cannot be JSON.”
It can. JSON.stringify reads own enumerable properties, and null-prototype objects can have those.
“setPrototypeOf is just a harmless assignment.”
It is standard, but engines document it as slow because it can invalidate assumptions about property lookup.
“dict.hasOwnProperty always works.”
It fails on null-prototype dictionaries because there is no inherited method. Use Object.hasOwn(dict, key).
“__proto__ is always a normal property name.”
On ordinary objects, assignment can trigger a legacy prototype setter. On null-prototype objects and Map, it is just data.
Practice: create, inspect, and count safely
5 EXERCISESRead the code and predict the three console lines.
const base = { kind: "base" };
const child = Object.create(base);
console.log(Object.getPrototypeOf(child) === base);
console.log("kind" in child);
console.log(Object.hasOwn(child, "kind"));The three lines are true, true, and false. The prototype link is exactly base; kind is visible through lookup; but child does not own kind.
The starter works for words such as apple but breaks for constructor. Make the dictionary safe, then check the expected count.
function countWords(words) {
const counts = {};
for (const word of words) {
counts[word] = (counts[word] || 0) + 1;
}
return counts;
}
console.log(countWords(["constructor", "constructor"]).constructor);function countWords(words) {
const counts = Object.create(null);
for (const word of words) {
counts[word] = (counts[word] || 0) + 1;
}
return counts;
}
console.log(countWords(["constructor", "constructor"]).constructor);Object.create(null) removes inherited names, so constructor starts missing instead of being inherited from Object.prototype. The fixed counter prints 2. A Map solution with get and set is also a good choice.
Create an object that delegates to shared, then answer whether the new object owns the method.
const shared = { describe() { return "shared method"; } };
const item = Object.create(shared);
item.name = "item";
console.log(item.describe());
console.log(Object.hasOwn(item, "describe"));item.describe() works through the prototype link, so it prints shared method. Object.hasOwn(item, "describe") is false because the method is inherited, not copied.
setPrototypeOf is discouragedComplete the advice in one word: prefer setting prototypes at ___ time.
Set prototypes at creation time. setPrototypeOf exists for special cases, but changing a live object’s prototype is documented as slow/deoptimizing and can make property lookup assumptions unstable.
Which method lets you iterate own key-value pairs without calling a method on the dictionary?
const dict = Object.create(null);
dict.apple = 2;
dict.constructor = 1;
for (const [key, value] of Object.entries(dict)) {
console.log(key + ":" + value);
}const dict = Object.create(null);
dict.apple = 2;
dict.constructor = 1;
for (const [key, value] of Object.entries(dict)) {
console.log(key + ":" + value);
}Object.entries(dict) reads own enumerable key-value pairs without needing dict to inherit anything. The loop prints apple:2 and constructor:1.
Quiz: check your understanding
7 QUESTIONSAnswer each question, then read the explanations for both right and wrong choices.
Question 1 of 7What does
Object.create(proto)do?Choose an answer to see the explanation.
Question 2 of 7What does this inherited property check print?
Read the code, then predictconst base = { answer: 42 }; const child = Object.create(base); console.log(Object.hasOwn(child, "answer"));Choose an answer to see the explanation.
Question 3 of 7Why is
Object.setPrototypeOf(obj, proto)discouraged in hot code?Choose an answer to see the explanation.
Question 4 of 7What does this null-prototype dictionary print?
Read the code, then predictconst dict = Object.create(null); dict.constructor = 2; console.log(dict.constructor);Choose an answer to see the explanation.
Question 5 of 7Which check works safely for a null-prototype dictionary?
Choose an answer to see the explanation.
Question 6 of 7What happens on a plain object when you assign
obj["__proto__"] = { polluted: true }?Choose an answer to see the explanation.
Question 7 of 7What does this print?
Read the code, then predictconst a = {}; try { Object.setPrototypeOf(a, a); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Key takeaways
Object.create(proto)creates a new object withprotoas its prototype; it does not copy methods.- The second argument defines property descriptors, whose flags default to
false. Object.getPrototypeOfinspects links;Object.setPrototypeOfchanges them but should be rare in hot code.Object.create(null)is a bare dictionary: no inheritedtoString,constructor, orhasOwnProperty.- Use
Object.hasOwn,Object.entries, orMapwhen dictionary keys come from data.
Remember the one-liner.Object.create chooses the prototype as an object is born; null-prototype objects choose no prototype at all.
Up next: Delegation vs classical inheritance.