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...inreads keys (like label slots on a mailbox);for...ofreads values (the letters inside)for...inalways yields string keys;for...ofyields actual values of any typefor...intraverses the prototype chain;for...ofignores it- Plain object?
for...in. Array, string, Map, Set?for...of for...inon arrays technically runs but causes real bugs
Quick example
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): usefor...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)withfor...ofis the safer path
Comparison table
| Aspect | for...in | for...of |
|---|---|---|
| Iterates over | Enumerable property keys (strings) | Values from iterables |
| Works on | Objects (including arrays) | Arrays, strings, Maps, Sets, generators |
| Prototype chain | Includes inherited properties | Ignored |
| Array behavior | Indices as strings ("0", "1") | Direct elements, holes become undefined |
| Typical use | Object key inspection | Collection 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
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
const obj = { a: 1, b: 2 };
for (const val of obj) { } // TypeError: obj is not iterablePlain objects have no [Symbol.iterator]. Fix: for (const val of Object.values(obj)).
Prototype pollution with for...in
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
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...inonreq.headersto log all incoming header names - React:
for...ofwhen building DOM lists or processing array props in custom loops - Node.js:
for...ofonprocess.argv.slice(2)to iterate CLI arguments - Lodash:
_.forInwrapsfor...insafely 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
const settings = { theme: "dark", lang: "en", debug: true };
for (const key in settings) {
console.log(`${key}: ${settings[key]}`);
}
// theme: dark
// lang: en
// debug: truefor...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
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 readyA concise answer to help you respond confidently on this topic during an interview.