Looping Objects Correctly: No Pitfalls

Posted by

A complete guide to iterating over objects in JavaScript — covering for...in, Object.keys, Object.entries, Object.values, and Maps—without the common mistakes.

A complete guide to iterating over objects in JavaScript — covering for...in, Object.keys, Object.entries, Object.values, and Maps—without the common mistakes.

Introduction

Looping through arrays in JavaScript is straightforward:

for (const item of arr) { ... }

But with objects, things get messy. Should you use for...in, Object.keys, Object.entries, or for...of with a Map? Why does for...in sometimes show weird inherited stuff? And why doesn’t for...of even work on objects?

This guide explains the right ways to loop objects, the pitfalls that trip up developers, and the modern, safe patterns you should be using in real-world projects.


1. Why for...of Doesn’t Work on Plain Objects

const user = { id: 1, name: "Ali" };

for (const x of user) {
// ❌ TypeError: user is not iterable
}

Plain objects aren’t iterable by default. Only arrays, Maps, Sets, and other iterable types work with for...of.

👉 To loop objects, you need keys, values, or entries.


2. The Old-School Way: for...in

const user = { id: 1, name: "Ali" };

for (const key in user) {
console.log(key, user[key]);
}

⚠️ Pitfalls of for...in:

  • Loops over all enumerable properties, including inherited ones.
  • If someone extends Object.prototype, those show up too.

Safer version:

for (const key in user) {
if (Object.hasOwn(user, key)) {
console.log(key, user[key]);
}
}

👉 Still, modern devs avoid for...in unless necessary.


3. Modern Standard: Object.keys

const user = { id: 1, name: "Ali" };

Object.keys(user).forEach((key) => {
console.log(key, user[key]);
});
  • Returns array of own enumerable string keys.
  • Doesn’t include inherited props or symbols.
  • Safe, predictable, and array-friendly.

4. Object.values: Just the Values

const scores = { Ali: 90, Laiba: 85, Umar: 75 };

for (const value of Object.values(scores)) {
console.log(value);
}

👉 Perfect when you only care about values, not keys.


5. Object.entries: Keys + Values

const settings = { darkMode: true, version: 2 };

for (const [key, value] of Object.entries(settings)) {
console.log(key, value);
}
  • Clean destructuring.
  • Pairs perfectly with map, filter, reduce.
  • Great for transformations:
const doubled = Object.fromEntries(
Object.entries({ a: 1, b: 2 }).map(([k, v]) => [k, v * 2])
);

6. When to Use Map Instead of Object

Objects weren’t originally designed for key iteration — they’re records. If you need guaranteed iteration order, arbitrary key types, or frequent insert/delete, use a Map.

const map = new Map([
["id", 1],
["name", "Ali"]
]);

for (const [k, v] of map) {
console.log(k, v);
}

Benefits of Map:

  • Iteration order is insertion order.
  • Keys can be anything (objects, functions).
  • Direct for...of support.

7. Practical Use Cases

A) Logging API response safely

const response = { status: 200, message: "OK" };
for (const [k, v] of Object.entries(response)) {
console.log(`${k}: ${v}`);
}

B) Counting values

const votes = { A: 2, B: 3, C: 1 };
const total = Object.values(votes).reduce((sum, v) => sum + v, 0);

C) Transforming keys

const obj = { first_name: "Ali", last_name: "Rahmat" };
const camel = Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k.replace(/_./g, s => s[1].toUpperCase()), v])
);

8. TypeScript Tips

A) Iterating with type safety

type User = { id: number; name: string };
const user: User = { id: 1, name: "Ali" };

for (const [k, v] of Object.entries(user) as [keyof User, User[keyof User]][]) {
console.log(k, v);
}

B) Generic object iteration helper

function forEachEntry<T extends object>(
obj: T,
fn: (key: keyof T, value: T[keyof T]) => void
) {
Object.entries(obj).forEach(([k, v]) =>
fn(k as keyof T, v as T[keyof T])
);
}

9. Edge Cases & Gotchas

  1. Symbols aren’t included in Object.keys, values, or entries. Use Object.getOwnPropertySymbols for those.
  2. Non-enumerable properties don’t show up. Use Object.getOwnPropertyNames.
  3. Order: For objects, property order is insertion order for string keys (in modern JS). Numeric-like keys ("1", "2") come first in ascending order.
  4. Inherited props: Only for...in includes them—avoid unless needed.

10. Performance Notes

  • for...in + hasOwn guard is slower than Object.keys.
  • For large objects, prefer Object.keys/entries with plain for loops over forEach for max speed.
  • For very dynamic data, consider using Map for direct iteration.

Conclusion

Looping over objects is deceptively tricky — but once you know the tools, you’ll never reach for for...in blindly again.

  • ✅ Use Object.keys/values/entries for predictable, modern iteration.
  • ✅ Use Map when you need iteration order, non-string keys, or frequent updates.
  • ⚠️ Avoid raw for...in unless you really want inherited props.
  • 🔒 Don’t forget about symbols and non-enumerables — they require separate APIs.

Pro Tip: Think of objects as records and Maps as collections. Pick the right tool for the iteration story you need.


Call to Action (CTA)

What’s your go-to way of looping over objects — Object.entries pipelines, for...in with guards, or Map all the way?
Drop your pattern in the comments, and share this with a teammate who still does for...in without a guard.

Leave a Reply

Your email address will not be published. Required fields are marked *