jsguides

Object.keys()

Object.keys(obj)

Object.keys() returns an array of a given object’s own enumerable string-keyed property names, in the same order as a for...in loop (except for...in also enumerates inherited properties).

This is the most commonly used of the three Object iteration methods. Use keys() when you need to count properties, iterate over them, transform their names, or check for the presence of specific keys. The complement methods Object.values() and Object.entries() return the value side or both sides respectively.

Syntax

Object.keys(obj)

Parameters

ParameterTypeDefaultDescription
objObjectrequiredThe object whose own enumerable string-keyed property names are to be returned.

Return Value

An array of strings representing the given object’s own enumerable string-keyed property names.

Examples

Basic usage

const obj = { a: 1, b: 2, c: 3 };

console.log(Object.keys(obj));
// ['a', 'b', 'c']

Plain objects return their string keys in insertion order. The result is a real array, so you can call map, filter, or forEach on it directly without converting from another data structure. This also works on arrays, though the output may surprise you the first time.

With an array

const arr = ['a', 'b', 'c'];

console.log(Object.keys(arr));
// ['0', '1', '2']

Arrays are objects under the hood, so Object.keys() treats array indices as string keys. That can be useful for sparse-array inspection, but it is usually the wrong tool for iterating array elements — use forEach or a for...of loop for that.

Order of keys

const obj = { 10: 'ten', 1: 'one', 2: 'two' };

console.log(Object.keys(obj));
// ['1', '2', '10'] — numeric keys sorted, then string keys in insertion order

Integer-like keys (non-negative integers without leading zeros) are sorted numerically before any other string keys. This means the declaration order in the source does not control the output when keys look like array indices. Non-integer string keys always appear in insertion order.

Non-enumerable properties are excluded

const obj = Object.create({}, {
  hidden: { value: 'secret', enumerable: false },
  visible: { value: 'public', enumerable: true }
});

console.log(Object.keys(obj));
// ['visible']

Properties added with Object.defineProperty default to non-enumerable. That keeps internal data out of keys(), values(), and entries() output, which is often the intended behavior when you want hidden metadata. Primitives get coerced to objects, but each type behaves a little differently.

Primitive values are coerced

Object.keys('hello');
// ['0', '1', '2', '3', '4']

Object.keys(42);
// []

Object.keys(null);
// throws TypeError

String primitives are auto-boxed to String objects, so Object.keys('hello') returns the character indices. Numbers have no enumerable own properties, so the result is an empty array. null and undefined throw a TypeError because they cannot be converted to objects.

Common Patterns

Iterating over object properties

const user = { name: 'Alice', age: 30, role: 'admin' };

Object.keys(user).forEach(key => {
  console.log(`${key}: ${user[key]}`);
});
// name: Alice
// age: 30
// role: admin

Iterating with keys() plus forEach is a direct way to walk an object when you need both the key and the value. For simpler cases where you only need to test whether an object has content, checking the length of the keys array is enough.

Checking if an object is empty

function isEmpty(obj) {
  return Object.keys(obj).length === 0;
}

console.log(isEmpty({}));       // true
console.log(isEmpty({ a: 1 })); // false

An empty-object check based on Object.keys().length is concise and reliable for plain objects. It does not detect non-enumerable properties or symbols, which is usually the intended behavior for this kind of utility. Once you have confirmed that an object has content, the next logical step is often to reshape its keys into a different format.

Transforming keys

const original = { firstName: 'Alice', lastName: 'Smith' };

const snakeCase = Object.keys(original).reduce((acc, key) => {
  const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
  acc[snakeKey] = original[key];
  return acc;
}, {});

console.log(snakeCase);
// { first_name: 'Alice', last_name: 'Smith' }

When you have finished transforming keys, the next practical question is often about the order in which they appear. Object dot keys follows a specific set of rules that determine whether integer like keys come first or whether string keys keep their insertion order. Understanding these rules helps you predict the output before you even run the code.

Property ordering

Object.keys() returns keys in the same order as for...in: integer-like keys in ascending numeric order, followed by other string keys in insertion order. This ordering is consistent across modern engines but was only formally specified in ES2015, so older polyfills or runtimes may differ. The distinction between integer-like and other keys matters when you iterate and need a predictable sequence. Integer-like keys are strings that look like valid array indices, meaning non-negative integers without leading zeros:

const mixed = { z: 1, a: 2, '3': 3, '1': 4, b: 5 };
console.log(Object.keys(mixed));
// ['1', '3', 'z', 'a', 'b'] — integer keys first, then insertion order

Knowing the ordering rules helps when you are building UI from an object’s keys and want the output to appear in a predictable sequence. If you need full control, sort the keys array yourself with .sort() before iterating. The integer-first ordering can surprise developers who expect keys to appear in declaration order, especially when objects mix numeric-looking keys with string keys in the same data structure.

Symbol keys are excluded

Object.keys() only returns string keys. Symbol-keyed properties are invisible to Object.keys(), Object.entries(), and for...in. Use Object.getOwnPropertySymbols() to retrieve symbol keys. This means symbol-keyed data is effectively private from most enumeration APIs, which is useful for metadata that should not appear in serialization or generic iteration.

const sym = Symbol('id');
const obj = { name: 'Alice', [sym]: 42 };

console.log(Object.keys(obj));                // ['name']
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]

Symbols stay hidden from keys() regardless of how the property was added. If you need a complete picture of every own property — enumerable and non-enumerable alike — reach for getOwnPropertyNames() instead.

Object.keys() vs Object.getOwnPropertyNames()

getOwnPropertyNames() returns all own string-keyed properties, including non-enumerable ones. Object.keys() only returns enumerable ones. Properties added via Object.defineProperty with enumerable: false appear in getOwnPropertyNames but not in keys.

const obj = {};
Object.defineProperty(obj, 'hidden', { value: 1, enumerable: false });
obj.visible = 2;

console.log(Object.keys(obj));               // ['visible']
console.log(Object.getOwnPropertyNames(obj)); // ['hidden', 'visible']

This distinction matters when you are working with built-in objects or libraries that use non-enumerable properties for internal state. keys() gives you the public API surface; getOwnPropertyNames() shows you everything.

Inherited properties are excluded

Object.keys() only returns own properties — it skips anything the object inherits from its prototype. This distinguishes it from for...in, which walks the prototype chain. In practice this means keys from Object.prototype (like toString or hasOwnProperty) never appear in the result, even if an object was created with a non-null prototype via Object.create().

const parent = { inherited: true };
const child = Object.create(parent);
child.own = true;

console.log(Object.keys(child)); // ['own']

Why Object.keys() is common

Object.keys() is often the first choice because it produces a plain array that works naturally with the rest of the array API. Once you have an array of keys, you can map, filter, reduce, or count them without extra conversion. That makes it a good bridge between object-shaped data and array-based processing. It is also a clear signal that you only care about own enumerable string keys, not symbols or inherited members.

When it is not enough

If you need values too, Object.entries() is usually better. If you need non-enumerable properties, Object.getOwnPropertyNames() is the right tool. Object.keys() is intentionally narrow, and that narrowness is part of its value: it does one job well and leaves the rest to adjacent APIs.

See Also