Episode 1 — Fundamentals / 1.23 — Objects

1.23.f — Looping Through Keys

In one sentence: JavaScript provides multiple ways to iterate over object properties — for...in (with prototype pitfalls), Object.keys, Object.values, and Object.entries — each suited to different tasks like rendering data, transforming shapes, and filtering properties.

Navigation: ← 1.23 Overview · 1.23.g — Practice Problems →


1. for...in — iterates over ALL enumerable properties

The for...in loop walks every enumerable property on the object and its prototype chain:

const user = { name: "Alice", age: 25, role: "admin" };

for (const key in user) {
  console.log(`${key}: ${user[key]}`);
}
// name: Alice
// age: 25
// role: admin

The prototype chain problem

for...in includes inherited enumerable properties — this can lead to surprises:

// Adding a method to Object.prototype (bad practice, but illustrates the point)
Object.prototype.customMethod = function() {};

const user = { name: "Alice", age: 25 };

for (const key in user) {
  console.log(key);
}
// name
// age
// customMethod  <-- unexpected! Inherited from prototype

// Clean up
delete Object.prototype.customMethod;

In practice, built-in methods like toString are non-enumerable so they do not appear. But third-party code or prototype extensions can add enumerable properties.


2. hasOwnProperty guard in for...in

To filter out inherited properties, add a guard:

const user = { name: "Alice", age: 25 };

for (const key in user) {
  if (user.hasOwnProperty(key)) {
    console.log(`${key}: ${user[key]}`);
  }
}
// Only own properties — safe

// Modern alternative — Object.hasOwn (ES2022)
for (const key in user) {
  if (Object.hasOwn(user, key)) {
    console.log(`${key}: ${user[key]}`);
  }
}

When for...in without a guard is acceptable

  • Working with plain objects you created yourself.
  • When you are sure no prototype pollution has occurred.
  • Even then, Object.keys is usually cleaner.

3. Object.keys(obj) — array of own enumerable keys

Returns a new array of the object's own enumerable string keys:

const user = { name: "Alice", age: 25, role: "admin" };

const keys = Object.keys(user);
console.log(keys); // ["name", "age", "role"]

// Iterate with for...of
for (const key of Object.keys(user)) {
  console.log(`${key}: ${user[key]}`);
}
// name: Alice
// age: 25
// role: admin

Useful patterns with Object.keys

// Count properties
const propCount = Object.keys(user).length;
console.log(propCount); // 3

// Check if object is empty
function isEmpty(obj) {
  return Object.keys(obj).length === 0;
}
console.log(isEmpty({}));   // true
console.log(isEmpty(user)); // false

// Filter keys
const stringKeys = Object.keys(user).filter(key => typeof user[key] === "string");
console.log(stringKeys); // ["name", "role"]

4. Object.values(obj) — array of own enumerable values

Returns a new array of the object's own enumerable property values:

const user = { name: "Alice", age: 25, role: "admin" };

const values = Object.values(user);
console.log(values); // ["Alice", 25, "admin"]

// Sum all numeric values
const scores = { math: 95, english: 88, science: 72, history: 91 };
const total = Object.values(scores).reduce((sum, score) => sum + score, 0);
console.log(total); // 346

const average = total / Object.keys(scores).length;
console.log(average); // 86.5

Check if a value exists

const permissions = { read: true, write: false, delete: false };

const hasAnyTrue = Object.values(permissions).some(v => v === true);
console.log(hasAnyTrue); // true

const allTrue = Object.values(permissions).every(v => v === true);
console.log(allTrue); // false

5. Object.entries(obj) — array of [key, value] pairs

Returns a new array where each element is a [key, value] pair:

const user = { name: "Alice", age: 25, role: "admin" };

const entries = Object.entries(user);
console.log(entries);
// [ ["name", "Alice"], ["age", 25], ["role", "admin"] ]

Iterating with entries

for (const entry of Object.entries(user)) {
  console.log(`${entry[0]}: ${entry[1]}`);
}
// name: Alice
// age: 25
// role: admin

Converting back — Object.fromEntries

const entries = [["name", "Alice"], ["age", 25], ["role", "admin"]];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: "Alice", age: 25, role: "admin" }

This enables transform pipelines:

const prices = { widget: 9.99, gadget: 24.99, doohickey: 4.99 };

// Double all prices
const doubled = Object.fromEntries(
  Object.entries(prices).map(([key, value]) => [key, value * 2])
);
console.log(doubled); // { widget: 19.98, gadget: 49.98, doohickey: 9.98 }

// Filter entries
const expensive = Object.fromEntries(
  Object.entries(prices).filter(([, value]) => value > 10)
);
console.log(expensive); // { gadget: 24.99 }

6. Destructuring in for...of with entries

Combine Object.entries with destructuring for the cleanest iteration:

const user = { name: "Alice", age: 25, role: "admin" };

for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}
// name: Alice
// age: 25
// role: admin

Why this is preferred

// Without destructuring — verbose
for (const entry of Object.entries(user)) {
  console.log(`${entry[0]}: ${entry[1]}`);
}

// With destructuring — clean and semantic
for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

Real-world: building HTML from object data

const userProfile = {
  Name: "Alice Johnson",
  Email: "alice@example.com",
  Role: "Admin",
  "Member since": "2023-01-15",
};

// Generate HTML list items
const listItems = Object.entries(userProfile)
  .map(([label, value]) => `<li><strong>${label}:</strong> ${value}</li>`)
  .join("\n");

console.log(listItems);
// <li><strong>Name:</strong> Alice Johnson</li>
// <li><strong>Email:</strong> alice@example.com</li>
// <li><strong>Role:</strong> Admin</li>
// <li><strong>Member since:</strong> 2023-01-15</li>

7. Property order in objects

JavaScript objects do have a defined iteration order (since ES2015), but it may surprise you:

The rules

  1. Integer-like string keys — sorted numerically, ascending.
  2. Other string keys — in insertion order.
  3. Symbol keys — in insertion order (only via Object.getOwnPropertySymbols).
const obj = {
  b: 2,
  a: 1,
  2: "two",
  1: "one",
  c: 3,
  10: "ten",
};

console.log(Object.keys(obj));
// ["1", "2", "10", "b", "a", "c"]
//  ↑ numeric keys sorted first, then string keys in insertion order

Why this matters

// If you use numeric-like keys, order may not match insertion:
const statusCodes = {
  404: "Not Found",
  200: "OK",
  500: "Server Error",
  301: "Moved",
};

console.log(Object.keys(statusCodes));
// ["200", "301", "404", "500"] — sorted numerically, NOT insertion order!

// If you need insertion order with numeric keys, use Map:
const orderedCodes = new Map([
  [404, "Not Found"],
  [200, "OK"],
  [500, "Server Error"],
  [301, "Moved"],
]);

for (const [code, msg] of orderedCodes) {
  console.log(`${code}: ${msg}`);
}
// 404: Not Found (insertion order preserved)
// 200: OK
// 500: Server Error
// 301: Moved

8. Comparison table — for...in vs Object.keys vs Object.entries

Featurefor...inObject.keysObject.valuesObject.entries
ReturnsKeys (one per iteration)Array of keysArray of valuesArray of [key, value]
Own properties only?No (includes prototype)YesYesYes
Enumerable only?YesYesYesYes
Symbol keys?NoNoNoNo
Can use array methods?No (it's a loop)Yes (.filter, .map, etc.)YesYes
DestructuringN/AN/AN/AYes[key, val]
Recommended?RarelyYesYesYes (most versatile)

When to use each

GoalBest choice
Just need keysObject.keys(obj)
Just need valuesObject.values(obj)
Need both key and valueObject.entries(obj)
Transform object shapeObject.fromEntries(Object.entries(obj).map(...))
Legacy code / prototype-aware iterationfor...in with hasOwnProperty guard

9. Advanced patterns

Grouping array items into an object

const people = [
  { name: "Alice", department: "Engineering" },
  { name: "Bob", department: "Marketing" },
  { name: "Charlie", department: "Engineering" },
  { name: "Diana", department: "Marketing" },
  { name: "Eve", department: "Design" },
];

const byDepartment = {};
for (const person of people) {
  const dept = person.department;
  if (!byDepartment[dept]) {
    byDepartment[dept] = [];
  }
  byDepartment[dept].push(person.name);
}

console.log(byDepartment);
// {
//   Engineering: ["Alice", "Charlie"],
//   Marketing: ["Bob", "Diana"],
//   Design: ["Eve"]
// }

Inverting keys and values

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

const inverted = Object.fromEntries(
  Object.entries(original).map(([key, value]) => [value, key])
);

console.log(inverted); // { 1: "a", 2: "b", 3: "c" }

Picking specific keys

function pick(obj, keys) {
  return Object.fromEntries(
    Object.entries(obj).filter(([key]) => keys.includes(key))
  );
}

const user = { name: "Alice", age: 25, email: "alice@example.com", role: "admin" };
const publicInfo = pick(user, ["name", "role"]);
console.log(publicInfo); // { name: "Alice", role: "admin" }

Omitting specific keys

function omit(obj, keys) {
  return Object.fromEntries(
    Object.entries(obj).filter(([key]) => !keys.includes(key))
  );
}

const user = { name: "Alice", password: "secret", token: "abc", role: "admin" };
const safe = omit(user, ["password", "token"]);
console.log(safe); // { name: "Alice", role: "admin" }

10. Real examples

Rendering object data as a list

const product = {
  name: "Wireless Mouse",
  brand: "TechCo",
  price: "$29.99",
  color: "Black",
  warranty: "2 years",
};

// Convert to display-friendly format
const specs = Object.entries(product).map(([key, value]) => ({
  label: key.charAt(0).toUpperCase() + key.slice(1),
  value,
}));

console.log(specs);
// [
//   { label: "Name", value: "Wireless Mouse" },
//   { label: "Brand", value: "TechCo" },
//   { label: "Price", value: "$29.99" },
//   { label: "Color", value: "Black" },
//   { label: "Warranty", value: "2 years" }
// ]

Transforming object shape — API response to frontend model

const apiUser = {
  user_id: 42,
  first_name: "Alice",
  last_name: "Johnson",
  email_address: "alice@example.com",
  is_active: true,
  created_at: "2024-01-15T12:00:00Z",
};

// Snake_case to camelCase conversion
function snakeToCamel(str) {
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}

const frontendUser = Object.fromEntries(
  Object.entries(apiUser).map(([key, value]) => [snakeToCamel(key), value])
);

console.log(frontendUser);
// {
//   userId: 42,
//   firstName: "Alice",
//   lastName: "Johnson",
//   emailAddress: "alice@example.com",
//   isActive: true,
//   createdAt: "2024-01-15T12:00:00Z"
// }

Key takeaways

  1. for...in iterates all enumerable keys including the prototype chain — always guard with hasOwnProperty or prefer Object.keys.
  2. Object.keys(obj) returns an array of own enumerable string keys — safe and chainable with array methods.
  3. Object.values(obj) returns an array of values — great for aggregation (sum, filter, etc.).
  4. Object.entries(obj) returns [key, value] pairs — the most versatile; combine with destructuring for clean loops.
  5. Object.fromEntries converts [key, value] pairs back to an object — enables map/filter/transform pipelines.
  6. Property order: integer-like keys sort numerically first, then other string keys in insertion order.
  7. For patterns like pick, omit, invert, and transform, the entries → transform → fromEntries pipeline is idiomatic modern JavaScript.

Explain-It Challenge

Explain without notes:

  1. Why is for...in potentially dangerous without a hasOwnProperty guard?
  2. What is the difference between Object.keys and Object.entries? When would you choose each?
  3. In what order do JavaScript objects enumerate their keys? What happens with numeric-like keys?

Navigation: ← 1.23 Overview · 1.23.g — Practice Problems →