skip to content

Enumerability and Key Ordering

Which keys a loop or a serializer can see, and in what order the engine hands them back. Interviewers ask because `for...in` walking inherited keys and integer-like keys jumping to the front both cause bugs people hit in production.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

4

In JavaScript, which property keys does a `for...in` loop visit that `Object.keys(obj)` does not return, and how do you guard a `for...in` loop against them?

level: juniorimportance: must knowfreq 78%

answer

  1. one loop climbs, the other does not
  2. prototype chain participates in the walk
  3. own versus inherited, enumerable only
  4. neither construct ever sees symbols
  5. guard each iteration with an own-key check

basics

~20 s

for...in walks the prototype chain and visits inherited enumerable string keys as well as own ones; Object.keys returns only the object's own enumerable string keys. Guard each iteration with Object.hasOwn(obj, key), or iterate Object.keys instead.

solid answer

~40 s

`for...in` enumerates **enumerable string-keyed properties, own and inherited**: it starts at the object and keeps walking up the prototype chain, so anything an ancestor object contributes shows up in the loop. `Object.keys` (and `Object.values`/`Object.entries`) returns only **own** enumerable string keys — no prototype walk. Neither one ever sees symbol keys, and neither sees properties whose `enumerable` flag is false, which is why class methods and an array's `length` stay out of both. The practical bug is a library or old code adding a property to `Object.prototype` or `Array.prototype`: every `for...in` in the codebase silently grows an extra iteration. The standard guard is `if (!Object.hasOwn(obj, key)) continue;` inside the loop (`Object.hasOwn` is ES2022; before it, `Object.prototype.hasOwnProperty.call(obj, key)`). Simpler still: loop over `Object.keys(obj)` and skip the question entirely.

code

javascript · 16 lines
javascript
const proto = { shared: 'from proto' };
const obj = Object.create(proto);
obj.own = 'mine';
Object.defineProperty(obj, 'hidden', { value: 'x', enumerable: false });

const seen = [];
for (const k in obj) seen.push(k);
console.log(seen);              // ["own", "shared"]
console.log(Object.keys(obj));  // ["own"]

const guarded = [];
for (const k in obj) {
  if (!Object.hasOwn(obj, k)) continue;
  guarded.push(k);
}
console.log(guarded);           // ["own"]

go deeper

for a junior

Be able to say plainly that for...in also visits inherited keys while Object.keys returns only the object's own keys, and that Object.keys is the safer default for iterating a data object.

for a middle

Explain the mechanics: for...in climbs the prototype chain filtering only on the enumerable flag, visits each name once, and skips symbols. Show the Object.hasOwn guard and say why the .call form was needed before ES2022.

for a senior

Demonstrate the production angle: how a dependency writing to Object.prototype changes the behaviour of every for...in in a process, and why you would ban for...in over externally-sourced data rather than rely on every author remembering the guard.

for a principal

Own the policy call — a lint rule forbidding unguarded for...in, an object-shape convention (null-prototype maps, or Map for dynamic keys), and how you keep untrusted JSON from reaching code that enumerates it in the first place.

## The two axes that decide visibility Every property lookup API in JavaScript filters on two independent things: **ownership** (is the property directly on this object, or on something further up its prototype chain?) and **enumerability** (does the property's `enumerable` attribute say it should show up in enumeration?). Almost every surprise in this area is one of those two axes behaving differently from what you assumed. `for...in` filters on enumerability only — it does **not** filter on ownership. `Object.keys` filters on both: own **and** enumerable. That single sentence explains the whole difference. ## What for...in actually does `for...in` starts at the target object, collects its enumerable string-keyed properties, then moves to `Object.getPrototypeOf(obj)` and repeats, all the way to the end of the chain (usually `Object.prototype`, whose own properties are all non-enumerable, which is why plain objects normally look clean). ```js const proto = { shared: 1 }; const obj = Object.create(proto); obj.own = 2; for (const k in obj) console.log(k); // "own", then "shared" console.log(Object.keys(obj)); // ["own"] ``` Two details worth knowing: - **A name is visited at most once.** If the object has its own `shared`, the inherited `shared` is not visited a second time — the loop sees the shadowing one and moves on. - **A non-enumerable own property still shadows.** If `obj` defines `shared` as non-enumerable, the loop visits neither the own one (not enumerable) nor the inherited one (already accounted for). That is a genuinely surprising corner. `for...in` also never visits symbol-keyed properties — symbols are simply not part of this enumeration at all. ## Why this causes real bugs The classic failure is prototype pollution of built-ins. If any dependency does `Object.prototype.helper = fn`, then every `for...in` over every plain object in the process gains a `helper` iteration. Code that builds a payload by copying keys, or that counts entries, or that validates "unknown field" lists, quietly changes behaviour with no local edit. That is exactly why old style guides insisted every `for...in` carry an own-property guard, and why modern code mostly stopped using `for...in` over data objects at all. A second, subtler case: your own prototypes. If you attach defaults with `Object.create(defaults)` or assign methods with `Proto.method = ...` (an ordinary assignment, so **enumerable**), those show up in `for...in` over instances. Class syntax avoids this — methods declared in a class body are non-enumerable — but hand-rolled prototype assignment is not. ## The guards ```js for (const key in obj) { if (!Object.hasOwn(obj, key)) continue; // ES2022 // ... } ``` Before ES2022 the idiom was `Object.prototype.hasOwnProperty.call(obj, key)`. Note the `.call` — writing `obj.hasOwnProperty(key)` is itself fragile: it breaks on objects created with `Object.create(null)` (no prototype, therefore no inherited `hasOwnProperty`) and on any object that happens to have its own `hasOwnProperty` key, which is common for objects built from untrusted JSON. The better default is not to guard but to choose an API that already filters on ownership: `Object.keys(obj)`, `Object.values(obj)`, `Object.entries(obj)`. These return arrays, so you also get `map`/`filter`/`reduce` for free and never have to think about the chain. ## When for...in is still the right tool It is the only built-in construct that walks the chain for you, so it is genuinely useful when you *want* inherited configuration — a defaults object behind an overrides object, for example, where reading every effective key including the inherited defaults is the point. It is also occasionally used for debugging, to see what an object actually exposes. Outside those cases, treat `for...in` over a data object as a smell: it answers a question ("what enumerable string keys are reachable from here?") that is almost never the question you meant to ask ("what fields does this record have?").

  • If an object has its own non-enumerable property with the same name as an inherited enumerable one, what does for...in do?
    It visits neither. `for...in` tracks names it has already accounted for as it climbs, so the own property shadows the inherited one — but the own one is itself skipped because it is non-enumerable. The name simply never appears in the loop, which surprises people who expect shadowing to be invisible to enumeration.
  • Why is `obj.hasOwnProperty(key)` considered unsafe as the guard, and what do you write instead?
    It relies on inheriting the method. An object created with `Object.create(null)` has no prototype, so the call throws; and an object parsed from untrusted JSON can carry its own `hasOwnProperty` key that shadows the real method and returns anything. Use `Object.hasOwn(obj, key)` (ES2022), or `Object.prototype.hasOwnProperty.call(obj, key)` on older baselines.
  • Does for...in enumerate symbol-keyed properties?
    No. `for...in` is defined over string-keyed properties only, so symbol keys are invisible to it regardless of their `enumerable` flag. The same is true of `Object.keys`/`values`/`entries` and of `JSON.stringify`. To read own symbol keys you need `Object.getOwnPropertySymbols` or `Reflect.ownKeys`.

saying these in an interview costs you the question

  • Claims for...in only visits the object's own properties
  • Says Object.keys walks the prototype chain like for...in
  • Believes for...in returns symbol keys too
  • Writes obj.hasOwnProperty(key) on a null-prototype object
  • Thinks non-enumerable properties still appear in for...in

context

open as a page

What does `Object.keys({ b: 1, 2: 'two', a: 3, 1: 'one' })` return, and what rule produces that order?

level: middleimportance: should knowfreq 52%

basics

~10 s

It returns ["1", "2", "b", "a"]. Own keys come back in a fixed order: array-index-like integer strings first in ascending numeric order, then the remaining string keys in the order they were created.

open as a page

In JavaScript, how do `Object.keys`, `Object.getOwnPropertyNames`, `Object.getOwnPropertySymbols` and `Reflect.ownKeys` differ in which of an object's own keys they return?

level: middleimportance: should knowfreq 45%

basics

~10 s

Object.keys returns own enumerable string keys; Object.getOwnPropertyNames returns all own string keys including non-enumerable ones; Object.getOwnPropertySymbols returns own symbol keys; Reflect.ownKeys returns every own key, strings then symbols.

open as a page

A class instance is passed to `JSON.stringify` and the class's getters and methods are missing from the output, while the constructor-assigned fields are there. Explain the enumerability rule behind that, and how you would get a computed value into the JSON.

level: seniorimportance: should knowfreq 42%

basics

~20 s

JSON.stringify serializes only own enumerable string-keyed properties. Class methods and getters live on the prototype, not the instance, and are non-enumerable, so they are skipped; constructor-assigned fields are own and enumerable. Add a toJSON method to include computed values.

open as a page