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)
ParameterDescription
objThe object whose own enumerable string-keyed property names are returned
Returnsstring[] — 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+):

  1. Integer-like keys — sorted in ascending numeric order
  2. String keys — in insertion order
  3. (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:

ExcludedWhy
Inherited propertiesNot own — they live on the prototype
Non-enumerable propertiesDefined with enumerable: false
Symbol-keyed propertiesOnly 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

FeatureObject.keys(obj)for...in loop
Inherited propertiesExcludedIncluded
Non-enumerableExcludedExcluded
Symbol keysExcludedExcluded
ReturnsArray (can use array methods)Iterates (loop only)
Needs hasOwnProperty check?NoYes (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

FeatureObject.keys(obj)Object.getOwnPropertyNames(obj)
Own propertiesYesYes
Enumerable onlyYesNo — includes non-enumerable
Symbol keysNoNo
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:

MethodOwnEnumerable onlyIncludes Symbols
Object.keys()YesYesNo
Object.getOwnPropertyNames()YesNoNo
Object.getOwnPropertySymbols()YesNoOnly Symbols
Reflect.ownKeys()YesNoYes (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

  1. Object.keys(obj) returns an array of own, enumerable, string-keyed property names.
  2. Order: integer-like keys first (sorted), then string keys in insertion order.
  3. Object.keys(obj).length === 0 is the standard empty-object check.
  4. Prefer Object.keys over for...in to avoid inherited property issues.
  5. Does not include Symbol keys or non-enumerable properties.
  6. Combine with array methods (filter, map, includes) for powerful object inspection.

Explain-It Challenge

Explain without notes:

  1. What is the difference between Object.keys(obj) and for...in?
  2. Given { 3: "c", 1: "a", name: "x", 2: "b" }, what order does Object.keys() return?
  3. 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 ->