Skip to main content

For...in vs for...of in JavaScript

for...in iterates over enumerable property keys (strings) of an object; for...of iterates over values from any iterable like arrays, strings, Maps, or Sets.

Theory

TL;DR

  • for...in reads keys (like label slots on a mailbox); for...of reads values (the letters inside)
  • for...in always yields string keys; for...of yields actual values of any type
  • for...in traverses the prototype chain; for...of ignores it
  • Plain object? for...in. Array, string, Map, Set? for...of
  • for...in on arrays technically runs but causes real bugs

Quick example

javascript
const obj = { a: 1, b: 2 }; const arr = [1, 2]; for (const key in obj) { console.log(key); // "a", "b" (string keys) } for (const val of arr) { console.log(val); // 1, 2 (actual values) }

for...in gives you keys as strings. for...of gives you values directly. That is the whole split.

Key difference

for...in was designed for objects. It walks all enumerable properties including those inherited through the prototype chain, and always yields string keys. for...of was introduced in ES2015 and works with anything that has a [Symbol.iterator] method. No prototype traversal, no string coercion. Just values in sequence.

When to use

  • Inspecting object structure (a config object, req.headers): use for...in
  • Looping through array elements, characters in a string, or Map/Set entries: use for...of
  • Need both keys and values from an object: for (const [k, v] of Object.entries(obj))
  • Want to avoid prototype chain surprises: Object.keys(obj) with for...of is the safer path

Comparison table

Aspectfor...infor...of
Iterates overEnumerable property keys (strings)Values from iterables
Works onObjects (including arrays)Arrays, strings, Maps, Sets, generators
Prototype chainIncludes inherited propertiesIgnored
Array behaviorIndices as strings ("0", "1")Direct elements, holes become undefined
Typical useObject key inspectionCollection traversal

How it works

for...in calls the internal enumerate operation, walks own and prototype properties, and yields each key as a string. That is why you get "0" instead of 0 on arrays.

for...of calls [Symbol.iterator]() on the target, then calls .next() in a loop, reading each {value, done} object until done is true. No prototype traversal. If the object has no [Symbol.iterator], you get a TypeError immediately.

Common mistakes

Using for...in on arrays

javascript
const arr = ["a", "b"]; for (const i in arr) { console.log(i, typeof i); // "0" "string", "1" "string" }

Indices become strings, not numbers. Sparse arrays behave unpredictably, and if any library added a property to Array.prototype, that shows up in the loop too. Use for...of or a plain for loop.

Assuming for...of works on plain objects

javascript
const obj = { a: 1, b: 2 }; for (const val of obj) { } // TypeError: obj is not iterable

Plain objects have no [Symbol.iterator]. Fix: for (const val of Object.values(obj)).

Prototype pollution with for...in

javascript
const arr = ["a", "b"]; Array.prototype.foo = "polluted"; for (const key in arr) { console.log(key); // "0", "1", "foo" }

This happens more often than you would expect in older codebases. Add a hasOwnProperty check or avoid for...in on arrays entirely.

Modifying an object during the loop

javascript
const obj = { a: 1, b: 2 }; for (const k in obj) { delete obj.b; // May skip "b" in V8 - behavior not guaranteed }

V8 snapshots keys before the loop starts. Deleting properties mid-iteration can cause skips. Collect keys first with Object.keys().

Real-world usage

  • Express: for...in on req.headers to log all incoming header names
  • React: for...of when building DOM lists or processing array props in custom loops
  • Node.js: for...of on process.argv.slice(2) to iterate CLI arguments
  • Lodash: _.forIn wraps for...in safely for object enumeration

Follow-up questions

Q: What does for...in produce on an array with holes?
A: It skips holes entirely since there is no property at that index. for...of yields undefined for each hole.

Q: How do you make a plain object work with for...of?
A: Wrap it with Object.values(obj), or add a custom [Symbol.iterator] generator method to the object.

Q: Why does for...in return string keys on arrays?
A: The spec coerces all property keys to strings. Arrays store elements as named properties, so index 0 becomes "0".

Q: Is iteration order guaranteed?
A: for...of on arrays and strings follows insertion order. for...in order is not guaranteed by the spec, though V8 typically returns own keys first, then prototype keys.

Q: (Senior) What happens in V8 when you delete a property during for...in?
A: V8 builds a key snapshot before the loop starts. Deleting a property after that point does not reliably remove it from iteration. The behavior is unspecified by the spec, so do not depend on it.

Examples

Inspecting a config object

javascript
const settings = { theme: "dark", lang: "en", debug: true }; for (const key in settings) { console.log(`${key}: ${settings[key]}`); } // theme: dark // lang: en // debug: true

for...in is clean for plain objects when you need the keys. This pattern shows up in config validation, logging middleware, and anywhere you inspect an object without knowing its shape in advance.

Building a list from an array

javascript
const todos = ["Buy milk", "Write code", "Sleep"]; const list = document.createElement("ul"); for (const todo of todos) { const item = document.createElement("li"); item.textContent = todo; list.appendChild(item); } // <ul><li>Buy milk</li><li>Write code</li><li>Sleep</li></ul>

for...of gives you elements directly. No index variable, no bracket access. This is the standard pattern for building DOM nodes or processing any ordered collection.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?