skip to content

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%

answer

  1. all four stop at the object itself
  2. two filters: key type and enumerability
  3. one of them hides nothing
  4. class methods explain the gap
  5. the widest one names both groups

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.

solid answer

~40 s

All four look only at **own** properties — none of them walks the prototype chain. They differ on two filters: enumerability and key type. `Object.keys` returns own **enumerable string** keys, which is what you want for data objects. `Object.getOwnPropertyNames` returns **all** own string keys regardless of the `enumerable` flag, so it surfaces things like an array's `length` or anything defined with `Object.defineProperty`, which defaults `enumerable` to false. `Object.getOwnPropertySymbols` returns own **symbol** keys, again regardless of enumerability. `Reflect.ownKeys` is the complete picture — effectively the names list followed by the symbols list, in spec order. In practice `Object.keys` is the everyday tool; the other three are for introspection, cloning that must preserve hidden or symbol-keyed state, and debugging a value that looks empty but clearly is not.

code

javascript · 13 lines
javascript
const tag = Symbol('tag');
const obj = { visible: 1, [tag]: 2 };
Object.defineProperty(obj, 'hidden', { value: 3, enumerable: false });

console.log(Object.keys(obj));                  // ["visible"]
console.log(Object.getOwnPropertyNames(obj));   // ["visible", "hidden"]
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(tag)]
console.log(Reflect.ownKeys(obj));              // ["visible", "hidden", Symbol(tag)]

class User { constructor(n) { this.name = n; } greet() { return this.name; } }
console.log(Object.keys(new User('Ada')));               // ["name"]
console.log(Object.keys(User.prototype));                // []
console.log(Object.getOwnPropertyNames(User.prototype)); // ["constructor", "greet"]

go deeper

for a junior

Know that Object.keys gives you the object's own visible string keys, and that there are separate calls for keys that Object.keys hides — the non-enumerable ones and the symbol-keyed ones.

for a middle

Lay out the grid cleanly: own-only for all four, then key type and enumerability as the two filters. Explain why an array's length and a class's methods are absent from Object.keys.

for a senior

Show the debugging instinct — reach for Reflect.ownKeys when an object looks empty but is not, and explain why key-list-based cloning quietly loses non-enumerable and symbol-keyed state that descriptor-based copying keeps.

for a principal

Own the API-design angle: what your objects expose to enumeration is part of their contract, since it determines what serializes, what clones, and what leaks into logs — decide deliberately which state is enumerable and which is hidden behind symbols or non-enumerable descriptors.

## One dimension they share, two they differ on All four APIs are **own-only**: they stop at the object itself and never consult `Object.getPrototypeOf(obj)`. That alone distinguishes the whole family from `for...in`, which is the only built-in construct that climbs the chain. Where they differ is a two-by-two grid: **string keys vs symbol keys**, and **enumerable-only vs regardless of enumerability**. | API | string keys | symbol keys | non-enumerable? | |---|---|---|---| | `Object.keys` / `values` / `entries` | yes | no | no | | `Object.getOwnPropertyNames` | yes | no | yes | | `Object.getOwnPropertySymbols` | no | yes | yes | | `Reflect.ownKeys` | yes | yes | yes | ```js const tag = Symbol('tag'); const obj = { visible: 1, [tag]: 2 }; Object.defineProperty(obj, 'hidden', { value: 3, enumerable: false }); Object.keys(obj); // ["visible"] Object.getOwnPropertyNames(obj); // ["visible", "hidden"] Object.getOwnPropertySymbols(obj); // [Symbol(tag)] Reflect.ownKeys(obj); // ["visible", "hidden", Symbol(tag)] ``` ## Where the hidden keys come from Non-enumerable properties are not exotic; the language creates them constantly. - **Arrays**: index properties are enumerable, but `length` is not. `Object.keys([10, 20])` gives `["0", "1"]`, while `Object.getOwnPropertyNames([10, 20])` gives `["0", "1", "length"]`. - **Class bodies**: methods, getters and setters declared in a class are installed on the prototype as **non-enumerable**. So they are missing from `Object.keys` twice over — not own, and not enumerable. Even `Object.keys(MyClass.prototype)` is empty; `Object.getOwnPropertyNames(MyClass.prototype)` shows `constructor` and the method names. - **`Object.defineProperty`**: when you do not pass a descriptor flag it defaults to `false`, so a property added this way is non-enumerable unless you say otherwise. Plain assignment (`obj.x = 1`) creates an enumerable property, which is why the two ways of adding a property behave so differently under enumeration. - **Built-in prototypes**: everything on `Object.prototype`, `Array.prototype` and friends is non-enumerable — that is precisely why `for...in` over a plain object does not drown in inherited methods. Symbol keys are the other invisible category. They are never returned by `Object.keys`, never visited by `for...in`, and never serialized by `JSON.stringify`, which makes them a reasonable place to park metadata you do not want travelling with the data. ## The order they come back in `Reflect.ownKeys` and `Object.getOwnPropertyNames` follow the specified own-key order: array-index-like keys first in ascending numeric order, then remaining string keys in creation order, then (for `Reflect.ownKeys`) symbols in creation order. `Object.keys` returns the same sequence with the non-enumerable string keys filtered out, so relative order is preserved. ## Choosing between them - **Iterating data** — `Object.keys` / `Object.entries`. You want the enumerable string fields and nothing else, and that is exactly the set a JSON round-trip would carry. - **Debugging "why is this object empty?"** — `Reflect.ownKeys`. If `console.log(Object.keys(x))` prints `[]` for an object that visibly has state, the state is non-enumerable, symbol-keyed, or living on the prototype. `Reflect.ownKeys` settles the first two immediately. - **Faithful copying** — a copy built from `Object.keys` (or spread, or `Object.assign`) silently drops non-enumerable properties and flattens getters into their current values. When a clone must preserve the full shape, you need the descriptor-based route rather than a key-list-based one. - **Framework and library code** — `Reflect.ownKeys` is the honest enumeration when you are reflecting over arbitrary user objects, because it is the same set the engine itself considers "the own keys of this object". One gotcha: these APIs coerce their argument. Since ES2015 `Object.keys('abc')` returns `["0", "1", "2"]` rather than throwing, because the primitive is boxed first. `Reflect.ownKeys`, by contrast, requires an actual object and throws a `TypeError` on a primitive — a small but real difference between the `Object.*` and `Reflect.*` families.

  • Why does `Object.keys` on an instance of a class return the constructor-assigned fields but none of the class's methods?
    Two reasons stack up. Methods declared in a class body live on the prototype, not on the instance, so they are not own properties; and class-body methods are installed as non-enumerable, so even `Object.keys(MyClass.prototype)` is empty. Fields assigned in the constructor are ordinary enumerable own properties, so they do show up.
  • Which key set does `Reflect.ownKeys` correspond to, and in what order?
    It is the object's complete own-key list — the same set as `Object.getOwnPropertyNames` followed by `Object.getOwnPropertySymbols`, with enumerability ignored entirely. Order is the specified one: array-index keys ascending, then other string keys in creation order, then symbol keys in creation order.
  • What happens if you pass a primitive string to these APIs?
    `Object.keys('abc')` boxes the primitive and returns `["0", "1", "2"]` — index properties are enumerable, `length` is not. `Reflect.ownKeys('abc')` throws a TypeError instead, because the Reflect family requires a real object argument. That asymmetry between Object.* and Reflect.* is easy to trip over in generic code.

saying these in an interview costs you the question

  • Thinks getOwnPropertyNames walks the prototype chain
  • Says Object.keys includes symbol keys
  • Believes defineProperty creates an enumerable property by default
  • Assumes class methods appear in Object.keys of the prototype
  • Claims Reflect.ownKeys filters out non-enumerable keys

context