Back to JavaScript
JavaScript
easy
mid

How would you implement a polyfill for Object.create?

Object.create(proto) makes a new object whose [[Prototype]] is proto. Classic polyfill: a temp constructor function F whose prototype is set to proto, then return new F(). Handle null proto and the optional propertiesDescriptor second argument via Object.defineProperties.

4 min read·~12 min to think through

Object.create(proto, [propertiesObject]) creates a new object with its prototype set to proto. The polyfill is a clever use of the only prototype-setting tool that existed before Object.create: new.

The classic polyfill

js
Object.create = Object.create || function (proto, propertiesObject) {
  // 1. validate proto
  if (typeof proto !== "object" && typeof proto !== "function" && proto !== null) {
    throw new TypeError("Object prototype may only be an Object or null");
  }

  // 2. the trick: a throwaway constructor whose .prototype is the proto arg
  function F() {}
  F.prototype = proto;
  const obj = new F();              // obj.[[Prototype]] === proto

  // 3. null proto: F.prototype can't truly be null, so patch after
  if (proto === null) {
    Object.setPrototypeOf
      ? Object.setPrototypeOf(obj, null)
      : (obj.__proto__ = null);
  }

  // 4. optional second arg — property descriptors
  if (propertiesObject !== undefined) {
    Object.defineProperties(obj, propertiesObject);
  }

  return obj;
};

Why the temp-constructor trick works

Before Object.create, the only way to control an object's prototype was new: new F() produces an object whose internal [[Prototype]] is F.prototype. So you set F.prototype = proto, do new F(), and you've created an object with the prototype you wanted — F itself is just a disposable vehicle (F() {} is empty, so no construction side effects).

The semantics to get right

  1. The prototype linkObject.create(p) → the new object delegates to p. Property lookups not found on the object walk up to p.
  2. proto === nullObject.create(null) makes a "bare" object with no prototype — no toString, hasOwnProperty, etc. Useful as a clean dictionary/map. The temp-constructor trick can't directly produce a null prototype (F.prototype = null falls back to Object.prototype), so you patch it after — which is why the polyfill is imperfect for the null case in truly old environments.
  3. Second argumentpropertiesObject is a property descriptors map (like the 2nd arg of Object.defineProperties), not plain key/values: { x: { value: 1, writable: true, enumerable: true } }.
  4. Validation — throw TypeError if proto isn't an object, function, or null.

The framing

"Object.create makes a new object with a given prototype. The polyfill leans on the only pre-existing prototype-setting mechanism — new: define an empty throwaway constructor F, set F.prototype = proto, and new F() gives you an object whose [[Prototype]] is proto. Then handle the edges: validate proto, special-case proto === null (the trick can't natively produce a null prototype, so patch it), and apply the optional second argument via Object.defineProperties since it takes descriptors, not values."

Follow-up questions

  • Why does the empty constructor function trick work?
  • What's special about Object.create(null)?
  • What form does the second argument take?
  • Why was Object.create added when `new` already existed?

Common mistakes

  • Treating the second argument as plain key/values instead of property descriptors.
  • Not handling proto === null.
  • Forgetting to validate the proto argument type.
  • Adding construction logic to F (it must be empty).

Performance considerations

  • Object creation is cheap. Object.create(null) dictionaries can be slightly faster for pure key/value use (no prototype chain to walk) and avoid prototype-key collisions.

Edge cases

  • proto === null — bare object, no inherited methods.
  • proto is a function (valid — functions are objects).
  • proto is a primitive — should throw TypeError.
  • Second argument with getters/setters in descriptors.

Real-world examples

  • Object.create(null) for safe hash maps free of prototype pollution.
  • Setting up prototype chains for inheritance before ES6 classes.

Senior engineer discussion

Seniors explain the new-operator trick as the historical mechanism, correctly handle the null-prototype edge and its imperfection, treat the second arg as descriptors, and validate the proto type.

Related questions