Object.create()

ES5+

Creates a new object whose internal prototype is set to the object you pass — or to null for a completely prototype-free object — optionally defining properties via descriptors in the same call. It is the most direct way to control the prototype chain at creation time.

Syntax

Object.create(proto, propertiesObject)

Parameters

proto Object | null

The object to be the prototype of the newly created object

propertiesObject Object optional

Object whose enumerable own properties specify property descriptors

Return Value

Object

A new object with the specified prototype and properties

Examples

JavaScript
const person = { greet() { return 'Hello!'; } };
const john = Object.create(person);
john.name = 'John';
console.log(john.greet());
console.log(john.name);
Output:
// 'Hello!' 'John'

📌 When to Use

Use Object.create() in two main situations. First, prototype-free dictionaries: Object.create(null) yields an object with no inherited members at all, so user-supplied keys like "toString", "constructor", or "__proto__" behave as ordinary data instead of colliding with Object.prototype — the classic defense against prototype-pollution bugs in objects used as maps, and still useful today when JSON-serializability makes a plain object preferable to a Map. Second, explicit prototype wiring: delegation-based designs, pre-class inheritance patterns (Child.prototype = Object.create(Parent.prototype)), and factories that stamp many instances sharing one method-holding prototype without running a constructor. The rarely-used second argument defines properties with full descriptors at creation, which occasionally beats a create-then-defineProperties sequence in metaprogramming code. Prefer ES6 classes for everyday inheritance — they express the same mechanics far more readably — and prefer Map for mutable keyed collections with arbitrary keys. Reach for Object.create() when you need what neither offers: a null prototype, a chosen prototype without constructor side effects, or precise descriptor control at the moment of birth.

⚠️ Common Mistakes

Interop surprises with null-prototype objects - they lack toString(), so string coercion like "" + obj throws a TypeError, and libraries that call hasOwnProperty directly on their inputs crash. Interoperate through Object.keys(), Object.hasOwn(), or spread the object into a normal literal before handing it to third-party code.

Believing properties are copied from the prototype - Object.create(proto) copies nothing; it links. Reading obj.greet finds greet by walking up to the prototype at lookup time, so a later change to the prototype method is visible through every object created from it - shared state that surprises people expecting snapshots.

Forgetting that Object.create(null) creates an object without any prototype - no toString(), hasOwnProperty(), etc.

Confusing the second parameter format - it uses property descriptors like defineProperty, not simple key-value pairs.

Using Object.create() when a simple object literal or class would be clearer and more maintainable.

✅ Best Practices

Clone while preserving accessors and hidden properties by combining it with descriptor extraction: Object.create(Object.getPrototypeOf(src), Object.getOwnPropertyDescriptors(src)) reproduces getters, setters, and non-enumerable flags that spread syntax would flatten into plain snapshot values.

Reach for Map instead of Object.create(null) when keys are added and removed heavily at runtime - Map handles arbitrary key types, tracks size for free, and iterates in insertion order; keep null-prototype objects for JSON-friendly, read-mostly lookup tables.

Use Object.create(null) for dictionary/map objects to avoid prototype pollution and ensure safe property access.

Prefer ES6 classes for inheritance in most cases - they are more readable and widely understood.

Use Object.create() for prototype-based composition patterns or when you need objects without inherited methods.

⚡ Performance Notes

Object.create() with a prototype argument is slightly slower than an object literal because the engine sets up a custom prototype link, but the difference only matters in factories called millions of times. Null-prototype objects change lookup behavior usefully: a missing key fails immediately instead of walking Object.prototype, and there is no risk of accidentally hitting an inherited member, which makes them predictable dictionaries. Be aware of engine internals, though — V8 historically moves objects with many dynamically added and deleted keys into slower dictionary mode, and null-prototype objects used as ever-growing maps often live there; Map is consistently fast for that churn-heavy workload. The descriptor-based second argument is slower than assigning plain properties because each descriptor must be validated, so define hot-path objects with literals when you do not need descriptor control. Method-holding prototypes shared across many instances also save memory versus copying function properties onto every object.

🌍 Real World Example

Safe Dictionary with No Prototype

This example contrasts an ordinary object with a null-prototype dictionary for storing user-controlled keys. The regular object leaks inherited members — "toString" in obj is true — and treats a "__proto__" key specially, which is the root of prototype-pollution vulnerabilities in web applications. The Object.create(null) version has no inherited members and stores even "__proto__" and "constructor" as plain, harmless data, which cacheUserData exploits to cache records under completely arbitrary user IDs. The same technique appears inside many parsers, routers, and query-string libraries.

// Problem with regular objects as dictionaries
const unsafeDict = {};
console.log('toString' in unsafeDict); // true (inherited)
console.log('constructor' in unsafeDict); // true (inherited)

// Prototype pollution vulnerability
unsafeDict['__proto__'] = { hacked: true };
// This could affect other objects in some cases

// Safe dictionary with Object.create(null)
function createSafeDict() {
  return Object.create(null);
}

const safeDict = createSafeDict();
console.log('toString' in safeDict); // false
console.log('constructor' in safeDict); // false

// Safe to use any key, including __proto__
safeDict['__proto__'] = 'just a regular value';
safeDict['constructor'] = 'also safe';
safeDict['user_data'] = { name: 'John' };

console.log(safeDict['__proto__']); // 'just a regular value'

// Use case: User-provided keys
function cacheUserData(userId, data) {
  const cache = createSafeDict();
  cache[userId] = data; // Safe even if userId is '__proto__'
  return cache;
}

const userCache = cacheUserData('__proto__', { name: 'Alice' });
console.log(userCache['__proto__']); // { name: 'Alice' }

Related Methods