Object.keys()

The safe replacement for for...in, which walks the prototype chain. Its one surprise is ordering: integer-like keys come first, in numeric order, regardless of how you wrote them.

Object static methodES5 (2009)Live demo
Common call
Object.keys(obj)
Returns
an array of own enumerable string keys
Replaces
for...in with a hasOwnProperty guard
Watch out
integer-like keys are reordered ahead of string keys
Object.keys(objectobject — Any object. Primitives are coerced — a string yields its indices, a number yields an empty array. null and undefined throw TypeError.type: object · required)
→ string[]

Demo

Live evaluation
Try:
Inputs
jsonstringa JSON object, e.g. {"a":1}
Output
Object.keys(JSON.parse('{"a":1,"b":2}'))
['a', 'b']

The second case is the one worth remembering. The keys were written in the order 2, b, 1, a — and they come back as 1, 2, b, a. Integer-like keys are always listed first in ascending NUMERIC order, and only then do string keys appear in insertion order. This is specified behaviour, not an implementation quirk, and it bites whenever an object is keyed by id and the order is assumed to be the order of insertion. Note also that keys are always strings, even when the source had numbers, and that nesting is not explored — only the top level.

Parameters

NameTypeRequiredDescription
objectobjectyesAny object. Primitives are coerced — a string yields its indices, a number yields an empty array. null and undefined throw TypeError.

Return value

string[] — The object OWN enumerable string keys. Never inherited ones, never symbols, never non-enumerable ones. Always strings, even for numeric keys.

Common patterns

Iterate safely
No prototype chain, no hasOwnProperty guard needed.
for (const key of Object.keys(obj)) { }
Count properties
Objects have no length.
const size = Object.keys(obj).length;
Check for emptiness
The idiomatic test.
const isEmpty = Object.keys(obj).length === 0;

Examples

1. Own keys
Object.keys({a: 1, b: 2})
Returns
['a', 'b']
2. Numeric first
Object.keys({b: 1, 2: 1, 1: 1})
Returns
['1', '2', 'b']
3. A string
Object.keys('abc')
Returns
['0', '1', '2']
4. A number
Object.keys(42)
Returns
[]
5. null throws
Object.keys(null)
Returns
TypeError: Cannot convert undefined or null to object
6. Symbols excluded
Object.keys({[Symbol('s')]: 1, a: 2})
Returns
['a']

Pitfalls

1. Integer-like keys are reordered
They sort numerically and come before every string key, whatever the insertion order. An object used as a map from numeric ids will iterate in id order rather than the order you built it — and a key like "01" is NOT integer-like, so it stays with the strings.
Not insertion order
Object.keys({b: 1, 2: 1, a: 1, 1: 1})
['1', '2', 'b', 'a']
Use a Map
new Map([["b", 1], [2, 1], ["a", 1]]).keys()
insertion order, guaranteed
2. null and undefined throw
Every other primitive is coerced quietly — a number gives an empty array, a string gives its indices — but these two throw. An object that might be missing needs a guard or a ?? {} fallback.
Throws
Object.keys(maybeNull)
TypeError: Cannot convert undefined or null to object
Fallback
Object.keys(maybeNull ?? {})
[]
3. Non-enumerable and symbol keys are invisible
Properties defined with defineProperty default to enumerable: false and simply do not appear, nor do symbol keys. Two objects can therefore have the same Object.keys result and completely different contents.
Hidden
const o = {};
Object.defineProperty(o, 'h', {value: 1});
Object.keys(o)
[]
See everything
Object.getOwnPropertyNames(o)
['h']
4. Keys are always strings
A numeric key comes back as its string form, so strict comparison against a number fails. Convert explicitly if the numeric value is what you need.
String, not number
Object.keys({1: "a"})[0] === 1
false
Convert it
Number(Object.keys({1: "a"})[0]) === 1
true

When to use

Use it
  • Iterating an object own properties
  • Counting properties, or testing for emptiness
  • Converting an object to an array to use array methods
  • Anywhere for...in appears with a hasOwnProperty guard
Reach for something else
  • You want the values → Object.values
  • You want both → Object.entries
  • You need insertion order for numeric keys → a Map
  • You need inherited or non-enumerable properties → getOwnPropertyNames, or for...in

Notes

Complexity
O(n) in the number of own properties
Return
A new array of strings; the object is not modified
CPython impl
V8: Builtins-object-keys
Memory
Allocates the key array
Thread-safe
Single-threaded; the object is only read

FAQ

They are in order — numeric order. The specification requires integer-like keys to be listed first, ascending, before any string key. Only string keys follow insertion order. If insertion order matters for numeric keys, use a Map, whose ordering is guaranteed for every key type.

Object.keys({2: "b", 1: "a"});   // ['1', '2']

History

ES5
Object.keys added, giving a safe alternative to for...in.
ES2015
Property enumeration order specified, formalising the integer-keys-first rule.
ES2017
Object.values and Object.entries completed the trio.