Episode 1 — Fundamentals / 1.24 — Object Methods
1.24.f — Object.keys()
In one sentence:
Object.keys(obj)returns an array of an object's own enumerable string-keyed property names, giving you a clean list for iteration, counting, and validation.
Navigation: <- 1.24.e — Object.is . 1.24.g — Object.seal ->
1. What does Object.keys() do?
It returns an array of strings representing all the own, enumerable, string-keyed property names of an object.
const user = { name: "Alice", age: 30, role: "admin" };
const keys = Object.keys(user);
console.log(keys); // ["name", "age", "role"]
console.log(keys.length); // 3
2. Syntax
Object.keys(obj)
| Parameter | Description |
|---|---|
obj | The object whose own enumerable string-keyed property names are returned |
| Returns | string[] — an array of property name strings |
If obj is not an object, it is coerced:
Object.keys("hi"); // ["0", "1"] (string indices)
Object.keys(42); // []
Object.keys(true); // []
3. Property order guarantee
Object.keys() returns properties in a defined order (since ES2015+):
- Integer-like keys — sorted in ascending numeric order
- String keys — in insertion order
- (Symbol keys are excluded entirely)
const obj = { b: 2, 10: "ten", a: 1, 2: "two", 1: "one" };
console.log(Object.keys(obj));
// ["1", "2", "10", "b", "a"]
// ^--- integer keys sorted ---^ ^-- string keys in insertion order --^
This order applies to Object.keys(), Object.values(), Object.entries(), and for...in (minus inherited).
What counts as an integer-like key? A string that, when converted to a 32-bit unsigned integer and back, gives the same string:
// Integer-like: "0", "1", "42", "4294967294"
// NOT integer-like: "01", "-1", "1.5", "4294967296" (exceeds max)
4. Only own enumerable string-keyed properties
Object.keys() excludes:
| Excluded | Why |
|---|---|
| Inherited properties | Not own — they live on the prototype |
| Non-enumerable properties | Defined with enumerable: false |
| Symbol-keyed properties | Only string keys are included |
const parent = { inherited: true };
const child = Object.create(parent);
child.own = "yes";
Object.defineProperty(child, "secret", {
value: "hidden",
enumerable: false,
});
child[Symbol("id")] = 42;
console.log(Object.keys(child)); // ["own"]
5. Common patterns
Counting properties
const obj = { a: 1, b: 2, c: 3, d: 4 };
console.log(Object.keys(obj).length); // 4
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
console.log(isEmpty([])); // true (arrays are objects)
Note: This only checks own enumerable properties. An object with only inherited or non-enumerable properties would appear "empty."
Iterating with forEach
const scores = { math: 95, science: 88, english: 92 };
Object.keys(scores).forEach(subject => {
console.log(`${subject}: ${scores[subject]}`);
});
// math: 95
// science: 88
// english: 92
Iterating with for...of
for (const key of Object.keys(scores)) {
console.log(`${key} = ${scores[key]}`);
}
Using with array methods
const product = { name: "Laptop", price: 999, stock: 50 };
// Check if a key exists
const hasPrice = Object.keys(product).includes("price");
console.log(hasPrice); // true
// Find keys matching a pattern
const config = { db_host: "localhost", db_port: 5432, api_key: "abc", db_name: "app" };
const dbKeys = Object.keys(config).filter(k => k.startsWith("db_"));
console.log(dbKeys); // ["db_host", "db_port", "db_name"]
6. Comparison: Object.keys vs for...in
| Feature | Object.keys(obj) | for...in loop |
|---|---|---|
| Inherited properties | Excluded | Included |
| Non-enumerable | Excluded | Excluded |
| Symbol keys | Excluded | Excluded |
| Returns | Array (can use array methods) | Iterates (loop only) |
Needs hasOwnProperty check? | No | Yes (to skip inherited) |
const parent = { inherited: true };
const child = Object.create(parent);
child.own = "yes";
// for...in includes inherited
for (const key in child) {
console.log(key);
}
// "own"
// "inherited" <-- from prototype!
// Object.keys does not
console.log(Object.keys(child)); // ["own"]
The classic for...in guard:
for (const key in child) {
if (child.hasOwnProperty(key)) {
console.log(key); // only "own"
}
}
Object.keys() eliminates the need for this guard entirely.
7. Comparison: Object.keys vs Object.getOwnPropertyNames
| Feature | Object.keys(obj) | Object.getOwnPropertyNames(obj) |
|---|---|---|
| Own properties | Yes | Yes |
| Enumerable only | Yes | No — includes non-enumerable |
| Symbol keys | No | No |
const obj = {};
Object.defineProperty(obj, "visible", { value: 1, enumerable: true });
Object.defineProperty(obj, "hidden", { value: 2, enumerable: false });
console.log(Object.keys(obj)); // ["visible"]
console.log(Object.getOwnPropertyNames(obj)); // ["visible", "hidden"]
For the full picture:
| Method | Own | Enumerable only | Includes Symbols |
|---|---|---|---|
Object.keys() | Yes | Yes | No |
Object.getOwnPropertyNames() | Yes | No | No |
Object.getOwnPropertySymbols() | Yes | No | Only Symbols |
Reflect.ownKeys() | Yes | No | Yes (all keys) |
8. Real-world examples
Validating object shape
function validateUser(obj) {
const required = ["name", "email", "age"];
const keys = Object.keys(obj);
const missing = required.filter(k => !keys.includes(k));
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(", ")}`);
}
return true;
}
validateUser({ name: "Alice", email: "a@b.com", age: 30 }); // true
// validateUser({ name: "Alice" }); // Error: Missing required fields: email, age
Detecting extra/unexpected properties
function strictValidate(obj, allowedKeys) {
const extra = Object.keys(obj).filter(k => !allowedKeys.includes(k));
if (extra.length > 0) {
console.warn(`Unexpected properties: ${extra.join(", ")}`);
}
}
strictValidate(
{ name: "Alice", age: 30, hack: true },
["name", "age", "email"]
);
// "Unexpected properties: hack"
Iterating for rendering (React-style)
const styles = { color: "red", fontSize: "16px", fontWeight: "bold" };
// Build inline style string
const styleString = Object.keys(styles)
.map(prop => `${prop}: ${styles[prop]}`)
.join("; ");
console.log(styleString);
// "color: red; fontSize: 16px; fontWeight: bold"
Creating a CSV header from object keys
const records = [
{ name: "Alice", age: 30, city: "NYC" },
{ name: "Bob", age: 25, city: "LA" },
];
const headers = Object.keys(records[0]).join(",");
const rows = records.map(r => Object.values(r).join(","));
const csv = [headers, ...rows].join("\n");
console.log(csv);
// name,age,city
// Alice,30,NYC
// Bob,25,LA
Comparing object shapes
function sameShape(a, b) {
const keysA = Object.keys(a).sort();
const keysB = Object.keys(b).sort();
return keysA.length === keysB.length &&
keysA.every((key, i) => key === keysB[i]);
}
console.log(sameShape({ a: 1, b: 2 }, { b: 3, a: 4 })); // true (same keys)
console.log(sameShape({ a: 1, b: 2 }, { a: 1, c: 3 })); // false
9. Edge cases
// Empty object
Object.keys({}); // []
// Array (indices become string keys)
Object.keys(["a", "b"]); // ["0", "1"]
// null / undefined -- throws TypeError
// Object.keys(null); // TypeError!
// Object.keys(undefined); // TypeError!
// String (each character index)
Object.keys("hello"); // ["0", "1", "2", "3", "4"]
// Number, boolean (no enumerable properties)
Object.keys(42); // []
Object.keys(true); // []
10. Performance considerations
Object.keys() creates a new array each time it is called. In hot paths:
// Avoid creating the array twice:
const keys = Object.keys(obj);
if (keys.length > 0) {
keys.forEach(key => { /* ... */ });
}
// NOT:
if (Object.keys(obj).length > 0) {
Object.keys(obj).forEach(key => { /* ... */ });
// Two array allocations!
}
For simple "has any own property" checks in performance-critical code, a for...in with early return can be faster (no array allocation):
function hasOwnProperties(obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) return true;
}
return false;
}
Key takeaways
Object.keys(obj)returns an array of own, enumerable, string-keyed property names.- Order: integer-like keys first (sorted), then string keys in insertion order.
Object.keys(obj).length === 0is the standard empty-object check.- Prefer
Object.keysoverfor...into avoid inherited property issues. - Does not include Symbol keys or non-enumerable properties.
- Combine with array methods (
filter,map,includes) for powerful object inspection.
Explain-It Challenge
Explain without notes:
- What is the difference between
Object.keys(obj)andfor...in? - Given
{ 3: "c", 1: "a", name: "x", 2: "b" }, what order doesObject.keys()return? - How would you check if an object has a specific set of required keys?
Navigation: <- 1.24.e — Object.is . 1.24.g — Object.seal ->