cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

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.

By the end, you can
  • 01
    Create with a chosen prototypeUse Object.create(proto, descriptors) and verify the link.
  • 02
    Pick dictionary storage safelyCompare {}, Object.create(null), and Map for user keys.
  • 03
    Keep prototype links stableExplain why setPrototypeOf is 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.

The short version

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.

Real-life analogyA shop chooses its warehouse

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.setPrototypeOf can 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 THROUGH

Object.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.

Create an object and inspect its link
Step 0 of 7Ready
Your turn: follow the blue line

Predict the inherited method, the prototype checks, and whether line 16 can change name. Then step through the recorded run.

Running in
  1. script
Next: line 2
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
"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);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Change the descriptor for line 9

Changing the descriptor starts a fresh run. Predict line 16 before stepping.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

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.

Creation-time alternative: object literal __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

INTERACTIVE

Object.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.

Switch a prototype, then see the caveats
Prototype toolsPop out in the code editor (opens in a new tab)JavaScript
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);}
Consolenot run

Press Run to execute the fixed example.

Try it yourself

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.

This is a demonstration of the API, not performance advice to use it in hot code.
Real-life analogySwitching warehouses mid-business

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.create set 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

INTERACTIVE

A 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.

Dictionary safety playground
The dictionary experimentPop out in the code editor (opens in a new tab)JavaScript
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));
Reading __proto__real outcomes
Plain objectprototype changed; polluted=true
Own __proto__?false
Null prototype[object Object]
hasOwnPropertyundefined
Mapvalue:__proto__
Size5

Before 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"}

Try it yourself
Read key:

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.

The inputs are fixed, not arbitrary code. The values are computed by real JavaScript in this component.
Choosing a dictionary shape
ChoiceBest forWatch out for
{} plain objectKnown record-like fields such as user.name or options.themeInherited names exist; __proto__ assignment has legacy behavior
Object.create(null)String-key dictionaries with untrusted or open-ended keysNo methods: use Object.hasOwn(dict, key) and Object.entries(dict)
MapFrequent add/delete, any key type, clear collection semanticsNot 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.

Practical performance guidance
  • Choose the prototype at creation time with a class, constructor, object literal __proto__, or Object.create.
  • Keep the chain shallow and predictable for objects used in hot code.
  • Do not use setPrototypeOf as 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.
Which dictionary should you choose?
  • A fixed options bag with trusted keys like theme and pageSize
  • 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)
Try it yourself
0 of 6 correct

Sort each scenario by the best default data structure. There are trade-offs, so read the explanations.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

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.

A safe word counter with a null-prototype dictionaryPop out in the code editor (opens in a new tab)JavaScript
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 EXERCISES
Exercise 1 · Warm-upPredict prototype checks

Read the code and predict the three console lines.

Starter codePop out in the code editor (opens in a new tab)JavaScript
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"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix the word counter

    The starter works for words such as apple but breaks for constructor. Make the dictionary safe, then check the expected count.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function countWords(words) {
      const counts = {};
      for (const word of words) {
        counts[word] = (counts[word] || 0) + 1;
      }
      return counts;
    }
    console.log(countWords(["constructor", "constructor"]).constructor);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeCreate an object with a chosen prototype

      Create an object that delegates to shared, then answer whether the new object owns the method.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const shared = { describe() { return "shared method"; } };
      const item = Object.create(shared);
      item.name = "item";
      console.log(item.describe());
      console.log(Object.hasOwn(item, "describe"));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeExplain why setPrototypeOf is discouraged

        Complete the advice in one word: prefer setting prototypes at ___ time.

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeIterate a null-prototype dictionary safely

          Which method lets you iterate own key-value pairs without calling a method on the dictionary?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const dict = Object.create(null);
          dict.apple = 2;
          dict.constructor = 1;
          for (const [key, value] of Object.entries(dict)) {
            console.log(key + ":" + value);
          }

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Answer each question, then read the explanations for both right and wrong choices.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does Object.create(proto) do?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this inherited property check print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const base = { answer: 42 };
              const child = Object.create(base);
              console.log(Object.hasOwn(child, "answer"));

              Choose an answer to see the explanation.

            3. Question 3 of 7Why is Object.setPrototypeOf(obj, proto) discouraged in hot code?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this null-prototype dictionary print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const dict = Object.create(null);
              dict.constructor = 2;
              console.log(dict.constructor);

              Choose an answer to see the explanation.

            5. Question 5 of 7Which check works safely for a null-prototype dictionary?

              Choose an answer to see the explanation.

            6. Question 6 of 7What happens on a plain object when you assign obj["__proto__"] = { polluted: true }?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const 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 with proto as its prototype; it does not copy methods.
            • The second argument defines property descriptors, whose flags default to false.
            • Object.getPrototypeOf inspects links; Object.setPrototypeOf changes them but should be rare in hot code.
            • Object.create(null) is a bare dictionary: no inherited toString, constructor, or hasOwnProperty.
            • Use Object.hasOwn, Object.entries, or Map when 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.

            CompleteFrontend Clear concepts. Working examples.