For In Vs For Of Javascript

7 min read

Understanding the distinction between for in vs for of javascript is a fundamental milestone for any developer moving beyond basic syntax into writing clean, performant, and bug-free code. While both statements iterate over data structures, they operate on fundamentally different principles: one enumerates property keys (enumerable properties), while the other iterates over values of iterable objects. Choosing the wrong loop often leads to unexpected bugs, prototype pollution issues, or performance bottlenecks, especially when dealing with Arrays, Objects, Maps, or Sets.

The Core Philosophical Difference

At the highest level, the difference boils down to what is being iterated Easy to understand, harder to ignore..

The for...Because of that, in statement iterates over enumerable property keys (strings or Symbols) of an object. It looks at the object itself and its prototype chain. It was designed primarily for inspecting or manipulating object structures—think configuration objects, dictionaries, or class instances where you need the keys to access values dynamically And that's really what it comes down to..

The for...It consumes the iterator provided by the object (via Symbol.of statement, introduced in ES6 (ES2015), iterates over **values** defined by an **iterable protocol**. iterator). It was designed for data consumption—processing items in a list, characters in a string, or entries in a Map/Set. It ignores the prototype chain entirely and does not care about property keys unless the iterator explicitly yields them.

Deep Dive: for...in — The Property Enumerator

Syntax and Mechanics

for (const key in object) {
  // key is a string (or Symbol)
  // object[key] gives the value
}

Critical Behaviors

  1. Prototype Chain Traversal: This is the most notorious behavior. for...in walks up the prototype chain. If Object.prototype has been extended (a common practice in older libraries or polyfills), those properties will appear in the loop.
  2. Enumerability Check: It only visits properties where enumerable: true (the default for properties created via assignment or object literals).
  3. Key Order: The specification dictates a specific order: integer indices first (sorted numerically), then string keys (in creation order), then Symbols (in creation order).
  4. Strings are Objects: Since strings are array-like objects, for...in iterates over their indices (keys "0", "1", "2"...), not the characters themselves.

The "Array Trap"

Using for...in on Arrays is widely considered an anti-pattern.

const arr = ['a', 'b', 'c'];
arr.customProp = 'metadata';

for (const index in arr) {
  console.That's why log(index); // "0", "1", "2", "customProp"
}
  • Order is not guaranteed for non-integer keys across all engines (though modern engines mostly follow spec). Practically speaking, ofand classicforloops for arrays significantly better thanfor... * Performance: Engines optimize for...* **Inherited properties** show up. Because of that, * **Keys are strings**, requiring parseIntorNumber() if you need math. in.

When to Actually Use for...in

  • Debugging/Inspection: console.log an unknown object's own keys.
  • Plain Objects (Dictionaries): Iterating over a hash map where keys are meaningful identifiers (e.g., userSettings, formData).
  • Reflection/Serialization: Building a query string or JSON serializer where you need keys and values.
  • Checking hasOwnProperty: If you must use it on objects with prototypes, always guard with Object.hasOwn(obj, key) (ES2022) or obj.hasOwnProperty(key).

Deep Dive: for...of — The Value Consumer

Syntax and Mechanics

for (const value of iterable) {
  // value is the actual element
}

The Iterable Protocol

An object is iterable if it implements the @@iterator method (accessible via Symbol.iterator). This method returns an iterator object with a next() method returning { value, done }. Built-in iterables include: Array, String, Map, Set, TypedArray, arguments, NodeList, and Generators Most people skip this — try not to..

Critical Behaviors

  1. Values, Not Keys: You get the data directly. No obj[key] lookup needed.
  2. No Prototype Pollution: It completely ignores the prototype chain. It only cares about the iterator the object provides.
  3. Order Guarantee: The order is defined by the iterator implementation. For Arrays, it is index order (0 to length-1). For Maps/Sets, it is insertion order.
  4. Destructuring Support: Works without friction with destructuring assignment.
    const map = new Map([['a', 1], ['b', 2]]);
    for (const [key, value] of map) { /* ... */ }
    
  5. Control Flow: break, continue, return, and throw work naturally and close the iterator (calling return() on the iterator if present).

When to Use for...of

  • Arrays & Array-likes: The default choice for looping lists.
  • Strings: Iterates Unicode code points (grapheme clusters) correctly, unlike index-based loops which split surrogate pairs.
  • Map & Set: The only clean syntax to iterate entries, keys, or values directly.
  • Generators/Async Iterators: Essential for consuming streams of data (use for await...of for async iterables).
  • DOM Collections: NodeList from querySelectorAll is iterable.

Head-to-Head Comparison Scenarios

Scenario 1: Standard Array

const colors = ['Red', 'Green', 'Blue'];

// for...in -> Keys (Strings)
for (const i in colors) console.log(i); // "0", "1", "2"

// for...On top of that, of -> Values
for (const c of colors) console. Which means log(c); // "Red", "Green", "Blue"

Verdict: for... of wins for readability and type safety (value is string, not string | "0" | "1" | "2") No workaround needed..

Scenario 2: Array with "Holes" (Sparse Arrays)

const sparse = [];
sparse[0] = 'A';
sparse[3] = 'D'; // Index 1, 2 are empty slots (holes)

for (const i in sparse) console.log(v); // "A", undefined, undefined, "D" (Visits holes as undefined)

Verdict: for...Day to day, log(i); // "0", "3" (Skips holes! Because of that, in skips holes; for... Day to day, of preserves index alignment by yielding undefined. ) for (const v of sparse) console.Choose based on whether holes represent "missing data" or "explicit undefined" Practical, not theoretical..

Scenario 3: Plain Object ({})

const user = { name: 'Alex', age: 30, role: 'Admin' };

// for...in -> Works perfectly
for (const k in user) console.log(k, user[k]);

// for...in` is native for plain objects. So of -> TypeError: user is not iterable
// Fix: Use Object. `for...Now, entries(user)) console. entries(user)
for (const [k, v] of Object.of` requires a helper (`Object.log(k, v);

Verdict: for...keys, values, entries).

Scenario 4: Map and Set


### Scenario 4: Map and Set
```javascript
const scores = new Map([['Alice', 10], ['Bob', 5]]);
for (const [name, points] of scores) {
  console.log(`${name}: ${points}`);
}
// → Alice: 10
// → Bob: 5

const tags = new Set(['js', 'css', 'html']);
for (const tag of tags) {
  console.log(tag);
}
// → js
// → css
// → html

Verdict: for…of is the natural fit. In real terms, it directly yields [key, value] pairs for Maps and single values for Sets, with clean destructuring. for…in would iterate over the internal [[MapData]] or [[SetData]] slots in a way that is both non‑standard and far less readable.


Scenario 5: Strings (Unicode Awareness)

const emoji = '👨‍👩‍👧‍👦'; // a single grapheme cluster

for (const ch of emoji) {
  console.log(ch); // logs the whole cluster once
}
// → 👨‍👩‍👧‍👦

for (const idx in emoji) {
  console.Which means log(idx); // "0"
}

Verdict: for…of iterates over Unicode code points (or more precisely, the abstract operation “list of iterable elements”), preserving grapheme clusters. for…in on a string only ever sees the index 0, making it useless for actual content traversal That's the part that actually makes a difference..


Scenario 6: Generators and Async Iterators

function* idGenerator() {
  let i = 1;
  while (i <= 3) yield i++;
}

for (const n of idGenerator()) {
  console.log(n); // 1, 2, 3
}

async function* asyncIds() {
  for (let i = 1; i <= 3; i++) yield i;
}
for await (const n of asyncIds()) {
  console.Also, log(n); // 1, 2, 3 (after awaiting each step)
}

Verdict: for…of (and for await…of) are the idiomatic ways to consume generator functions and async iterables. There is no equivalent “for…in” construct for these dynamic sources Which is the point..


Scenario 7: DOM Collections

const items = document.querySelectorAll('li');

for (const li of items) {
  li.classList.add('highlight');
}
// All 
  • elements now have the class "highlight". // for…in would give you the indices as strings ("0", "1", …) but // lacks the direct element reference and modern destructuring support.
  • Verdict: for…of works directly on live NodeList and HTMLCollection objects, providing the element itself rather than a string index. It also keeps the code readable and chainable.

    Real talk — this step gets skipped all the time That's the part that actually makes a difference..


    Quick Reference Table

    Use‑Case Recommended Loop Why
    Simple array / array‑like for…of Direct values, no index handling, destructuring friendly
    Sparse arrays (holes) for…in Skips holes; `
    Out the Door

    Recently Added

    In the Same Zone

    You May Enjoy These

    Thank you for reading about For In Vs For Of Javascript. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
    ⌂ Back to Home