DEV Community

Cover image for You use `obj.hasOwnProperty(key)` to check property ownership. It can silently fail. `Object.hasOwn()` is the safe replacement.
Parsa Jiravand
Parsa Jiravand

Posted on

You use `obj.hasOwnProperty(key)` to check property ownership. It can silently fail. `Object.hasOwn()` is the safe replacement.

Every JavaScript codebase has this pattern somewhere:

if (obj.hasOwnProperty(key)) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

It's idiomatic, it's readable, and most of the time it works. But there are two specific situations where it silently fails — one throws, one returns wrong results — and they come up more often than you'd expect. Object.hasOwn() is the ES2022 fix.

How hasOwnProperty can go wrong

Case 1: null-prototype objects. Objects created with Object.create(null) have no prototype. That's actually why people reach for them — as pure dictionaries where no inherited key can ever accidentally collide with a user-supplied key. The catch: they don't inherit hasOwnProperty either.

const dict = Object.create(null);
dict.name = "parsa";

dict.hasOwnProperty("name"); // ❌ TypeError: dict.hasOwnProperty is not a function
Enter fullscreen mode Exit fullscreen mode

This pattern is common in parsers, caches, and config objects. When you encounter one and call .hasOwnProperty() on it, you get a runtime error, not a boolean.

Case 2: shadowed method. Because hasOwnProperty is inherited from Object.prototype, any object can define its own hasOwnProperty property that overrides the inherited one. This is rarely malicious but it does happen with deserialized data or objects built from untrusted input:

const untrusted = {
  hasOwnProperty: () => true, // overrides the prototype method
  name: "parsa",
};

untrusted.hasOwnProperty("admin"); // ❌ true — completely wrong
Enter fullscreen mode Exit fullscreen mode

The inherited method is gone, replaced by a local property that returns whatever it wants.

The pre-ES2022 workaround

The fix that predates Object.hasOwn() is verbose but correct:

Object.prototype.hasOwnProperty.call(obj, key);
Enter fullscreen mode Exit fullscreen mode

This goes directly to Object.prototype for the real method, then calls it with obj as the receiver. It handles null-prototype objects (the method comes from the prototype explicitly, not from the object) and it can't be shadowed (you're not reading the method from the object at all).

This is also what ESLint's no-prototype-builtins rule has been pushing you toward since 2016. If you've seen that rule flag obj.hasOwnProperty(key) in a project and wondered why — this is the reason.

The problem is that Object.prototype.hasOwnProperty.call(obj, key) is 39 characters and easy to get wrong. It's the kind of code you copy-paste without fully understanding, which is exactly when bugs hide.

Object.hasOwn() — the clean version

Object.hasOwn(obj, key);
Enter fullscreen mode Exit fullscreen mode

Same semantics as Object.prototype.hasOwnProperty.call(obj, key), but readable at a glance.

const dict = Object.create(null);
dict.name = "parsa";

Object.hasOwn(dict, "name");    // ✅ true
Object.hasOwn(dict, "admin");   // ✅ false

const untrusted = {
  hasOwnProperty: () => true,
  name: "parsa",
};

Object.hasOwn(untrusted, "admin"); // ✅ false — can't be shadowed
Object.hasOwn(untrusted, "name");  // ✅ true
Enter fullscreen mode Exit fullscreen mode

Because Object.hasOwn is a static method on Object itself, there's nothing on the object being checked that can interfere with it.

Where this comes up in real code

The most common place you'll find this pattern is in loops over object keys:

for (const key in someObject) {
  if (Object.hasOwn(someObject, key)) {
    // skip inherited keys
  }
}
Enter fullscreen mode Exit fullscreen mode

The for...in loop walks the entire prototype chain. When you only care about own properties, you need to filter. Object.hasOwn() is the right tool.

It also turns up in validation logic, where you're checking that a required field actually exists on an incoming payload rather than on the payload's prototype:

function validate(payload) {
  const required = ["id", "name", "email"];
  const missing = required.filter(field => !Object.hasOwn(payload, field));
  if (missing.length) throw new Error(`Missing: ${missing.join(", ")}`);
}
Enter fullscreen mode Exit fullscreen mode

And in generic utility functions that need to handle arbitrary objects, including null-prototype ones:

function pick(obj, keys) {
  return Object.fromEntries(
    keys
      .filter(k => Object.hasOwn(obj, k))
      .map(k => [k, obj[k]])
  );
}
Enter fullscreen mode Exit fullscreen mode

TypeScript note

TypeScript accepts Object.hasOwn() and narrows the type in the same contexts where in narrows:

function process(config: unknown) {
  if (typeof config === "object" && config !== null && Object.hasOwn(config, "timeout")) {
    // TypeScript knows config has a "timeout" property here
    console.log((config as { timeout: number }).timeout);
  }
}
Enter fullscreen mode Exit fullscreen mode

It doesn't give you automatic narrowing of the property's type (that's a separate constraint), but it does narrow the object to non-null.

Browser support

Object.hasOwn() is Baseline 2022: Chrome 93, Firefox 92, Safari 15.4, Node.js 16.9. It's been in every major runtime for nearly four years. There's no polyfill concern for greenfield projects, and even the Object.prototype.hasOwnProperty.call() workaround works identically in older environments if you need it.

ESLint's no-prototype-builtins rule (enabled by eslint:recommended) will flag any direct .hasOwnProperty() call on an object and suggest exactly this migration.

🎮 Try it yourself

▶️ Open the interactive playground →

Runs right in your browser — poke at it and watch the concept react live.

🧠 Test yourself

Think it clicked? Take the 8-question quiz →

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

The takeaway

Search your codebase for .hasOwnProperty(. Every match is a candidate for Object.hasOwn(). The swap is mechanical — obj.hasOwnProperty(key) becomes Object.hasOwn(obj, key) — but the result is immune to the two silent failure modes that the old pattern can't handle. If you're writing a library, a plugin system, or anything that accepts external objects as input, this is the version you want.


Thanks for reading! Let's stay connected:

Top comments (0)