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 optionalObject whose enumerable own properties specify property descriptors
Return Value
A new object with the specified prototype and properties
Examples
const person = { greet() { return 'Hello!'; } };
const john = Object.create(person);
john.name = 'John';
console.log(john.greet());
console.log(john.name); 📌 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' }