Object.keys()
ES5+Returns an array of a given object's own enumerable string-keyed property names, in the same order a for...in loop would produce them but without walking the prototype chain. It is the standard way to turn an object's keys into an array you can iterate, filter, or map with the full array toolkit.
Syntax
Object.keys(obj)Parameters
obj Object The object whose enumerable own property names are to be returned
Return Value
An array of strings representing the object's enumerable properties
Examples
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); 📌 When to Use
Use Object.keys() when you need to iterate over property names, count properties, check whether an object holds any data at all, or transform keys into a different shape. Because it returns a plain array, it unlocks the entire array toolkit for objects, which have no iteration methods of their own: map(), filter(), reduce(), some(), and every() all become available. Typical scenarios include rendering a dynamic form where each key becomes an input field, validating that a payload contains only allowed keys before sending it to an API, building table headers from the first row object of a dataset, and generating cache keys or query strings from parameter objects. It is also the idiomatic emptiness check: Object.keys(obj).length === 0 reliably tells you whether a plain object has any own enumerable properties. Prefer Object.keys() over for...in when you only want the object's own properties, since for...in also walks the prototype chain and needs a hasOwnProperty guard. If you need the values, or both halves of each pair, reach for Object.values() or Object.entries() instead — all three share the same ordering rules, so their results line up index by index.
⚠️ Common Mistakes
Forgetting that only string-keyed properties are returned - Symbol keys are always skipped. If an object stores data under Symbol keys (common in libraries that want collision-free metadata), Object.keys() will not reveal them; you need Object.getOwnPropertySymbols() or Reflect.ownKeys() to see the complete picture of what the object holds.
Assuming integer-like keys keep insertion order - JavaScript engines list integer-like string keys ("0", "1", "42") first in ascending numeric order, before other string keys in insertion order. An object like { b: 1, 2: 2, a: 3 } yields ["2", "b", "a"], which surprises code that expects pure insertion order and can misalign data that is zipped against another list.
Passing null or undefined - Object.keys(null) throws a TypeError. When the input may come from an optional API field, guard with Object.keys(obj ?? {}) or validate first. Primitives like numbers and booleans are coerced to wrapper objects and simply return an empty array, which can silently hide bugs instead of surfacing them.
Expecting inherited properties to be included - Object.keys() only returns own enumerable properties, not inherited ones from the prototype chain.
Assuming a specific order - while modern JavaScript engines maintain insertion order, relying on it for critical logic can be risky in edge cases.
Using Object.keys() on arrays - while it works, it returns string indices. Use array methods like forEach() or map() for arrays instead.
✅ Best Practices
When you might mutate the object while looping over it, iterate the snapshot Object.keys() gives you - the returned array is a stable copy, so adding or deleting properties inside the loop body cannot affect the iteration, unlike a live for...in scan where mutation during enumeration has unpredictable results.
Validate incoming payloads by comparing key sets: const allowed = new Set(["name", "email"]); Object.keys(input).every(k => allowed.has(k)) rejects unexpected fields early and protects APIs from silently accepting typos or injected properties before they reach your database layer.
Combine with map() or forEach() for transformations: Object.keys(obj).map(key => ({ key, value: obj[key] }))
Use Object.keys(obj).length to count properties instead of manually counting with a loop.
For checking if an object is empty, use Object.keys(obj).length === 0 as a reliable pattern.
⚡ Performance Notes
Object.keys() allocates a new array on every call and walks all own enumerable properties, so it is O(n) in both time and space. V8 and other engines optimize objects with stable shapes (hidden classes), and enumerating keys on such objects is very fast; objects whose shape changes constantly, or dictionary-mode objects with thousands of keys, enumerate more slowly. In hot loops, avoid calling Object.keys(obj) repeatedly on the same unchanged object — hoist the call out of the loop and reuse the array. For emptiness checks in extremely hot code, a for...in loop that returns on the first own property avoids building the whole array, though the readability of Object.keys(obj).length === 0 usually wins. If you also need values, one Object.entries() pass is cheaper than separate keys() and values() calls followed by parallel indexing.
🌍 Real World Example
Form Validation with Dynamic Fields
This example validates a user registration form by treating the form state as data. Object.keys() lists every submitted field, filter() keeps only the required ones that are empty, and the result doubles as both a boolean validity flag and a list of field names you can highlight in the UI. Because the validator iterates keys dynamically, you can add new form fields later without touching the validation logic — a pattern that scales to any form shape and is common in form libraries.
const formData = {
username: 'john_doe',
email: '',
password: 'secret123'
};
const requiredFields = ['username', 'email', 'password'];
function validateForm(data) {
const emptyFields = Object.keys(data)
.filter(key => requiredFields.includes(key) && !data[key]);
if (emptyFields.length > 0) {
return { valid: false, errors: emptyFields };
}
return { valid: true, errors: [] };
}
console.log(validateForm(formData));
// { valid: false, errors: ['email'] }