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, andObject.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.keysis 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
- Integer-like string keys — sorted numerically, ascending.
- Other string keys — in insertion order.
- 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
| Feature | for...in | Object.keys | Object.values | Object.entries |
|---|---|---|---|---|
| Returns | Keys (one per iteration) | Array of keys | Array of values | Array of [key, value] |
| Own properties only? | No (includes prototype) | Yes | Yes | Yes |
| Enumerable only? | Yes | Yes | Yes | Yes |
| Symbol keys? | No | No | No | No |
| Can use array methods? | No (it's a loop) | Yes (.filter, .map, etc.) | Yes | Yes |
| Destructuring | N/A | N/A | N/A | Yes — [key, val] |
| Recommended? | Rarely | Yes | Yes | Yes (most versatile) |
When to use each
| Goal | Best choice |
|---|---|
| Just need keys | Object.keys(obj) |
| Just need values | Object.values(obj) |
| Need both key and value | Object.entries(obj) |
| Transform object shape | Object.fromEntries(Object.entries(obj).map(...)) |
| Legacy code / prototype-aware iteration | for...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
for...initerates all enumerable keys including the prototype chain — always guard withhasOwnPropertyor preferObject.keys.Object.keys(obj)returns an array of own enumerable string keys — safe and chainable with array methods.Object.values(obj)returns an array of values — great for aggregation (sum, filter, etc.).Object.entries(obj)returns[key, value]pairs — the most versatile; combine with destructuring for clean loops.Object.fromEntriesconverts[key, value]pairs back to an object — enables map/filter/transform pipelines.- Property order: integer-like keys sort numerically first, then other string keys in insertion order.
- For patterns like pick, omit, invert, and transform, the
entries → transform → fromEntriespipeline is idiomatic modern JavaScript.
Explain-It Challenge
Explain without notes:
- Why is
for...inpotentially dangerous without ahasOwnPropertyguard? - What is the difference between
Object.keysandObject.entries? When would you choose each? - In what order do JavaScript objects enumerate their keys? What happens with numeric-like keys?
Navigation: ← 1.23 Overview · 1.23.g — Practice Problems →