for...in vs for...of on an array
The difference between for...in and for...of is that the first iterates over keys and the second over values: on an array for...in yields indices as strings, while for...of yields the elements themselves. It is one of those things you need to understand precisely, because arrays are where the difference bites most often.
Theory
TL;DR
for...initerates over keys and indices, the names of enumerable properties, and is meant for objects.for...ofiterates over the values of iterables:Array,String,Set,Mapand others.- On an array
for...inreturns indices as strings ('0','1'), not as numbers. for...inwill also surface properties added to the array by hand and enumerable properties from the prototype.for...ofguarantees element order,for...ingives no order guarantee for non-numeric keys.- Use
for...offor arrays, andfor...inorObject.entries()for objects.
Quick example
const arr = ['a', 'b', 'c'];
for (const index in arr) {
console.log(index); // '0', '1', '2', strings
}
for (const value of arr) {
console.log(value); // 'a', 'b', 'c', values
}for...in iterates over keys
const arr = ['a', 'b', 'c'];
for (const index in arr) {
console.log(index); // '0', '1', '2'
console.log(typeof index); // 'string'
}What follows from this:
- the loop returns string indices, not numbers, so
index + 1gives'01'rather than1; - it was designed for objects, not for arrays;
- it can pick up extra properties if they were added by hand or put on the prototype.
for...of iterates over values
const arr = ['a', 'b', 'c'];
for (const value of arr) {
console.log(value); // 'a', 'b', 'c'
}What follows from this:
- the loop works with iterables:
Array,String,Set,Map,arguments,NodeList; - it returns the element values, not the keys;
- it is the ideal choice for an array.
When you need the index and the value at once, use entries():
for (const [index, value] of arr.entries()) {
console.log(index, value); // 0 'a', 1 'b', 2 'c'
}Here index is already a number, not a string.
The difference in practice: extra properties
const arr = ['x', 'y', 'z'];
arr.custom = 'hello'; // add a property by hand
for (const key in arr) {
console.log(key); // '0', '1', '2', 'custom', an unwanted key
}
for (const value of arr) {
console.log(value); // 'x', 'y', 'z', elements only
}for...in walks every enumerable property of the object, including ones added by hand and ones inherited from the prototype. for...of takes only the array elements.
This is exactly why for...in code is so often "rescued" by a guard that is only needed because the wrong loop was chosen:
for (const key in arr) {
if (!Object.hasOwn(arr, key)) continue; // a crutch you do not need with for...of
console.log(arr[key]);
}One more detail: for...in skips holes in a sparse array, while for...of walks them and yields undefined.
const sparse = [1, , 3];
for (const key in sparse) console.log(key); // '0', '2'
for (const value of sparse) console.log(value); // 1, undefined, 3Strings and other iterables
for (const char of 'JS') {
console.log(char); // 'J', 'S'
}for...of works beautifully with strings, Set and Map:
const set = new Set([1, 2, 2, 3]);
for (const value of set) console.log(value); // 1, 2, 3
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) console.log(key, value); // 'a' 1, 'b' 2for...in on a string returns the indices '0', '1', and on a Set or a Map it gives nothing useful, because their data does not live in enumerable properties.
Comparison table
| Feature | for...in | for...of |
|---|---|---|
| Iterates over | keys (indices, properties) | values |
| Works with | objects, arrays | iterables (array, string, Set, Map) |
| Returns | strings, property names | the values themselves |
| Iteration order | unpredictable for non-numeric keys | preserves the iterator order |
| Sees added properties | Yes | No |
| Good for arrays | No | Yes |
| Good for objects | Yes | No (not without Object.entries()) |
A simple rule worth remembering:
for...initerates over keys and is for objects.for...ofiterates over values and is for arrays and iterables.
Common mistakes
- Reaching for
for...inon an array. This is the most common mistake: the loop hands you string indices and picks up extra properties. - Treating the index as a number. In
for...inthe index is a string, soindex + 1concatenates into'01'. You needNumber(index)orarr.entries(). - Trying
for...ofon a plain object. A plain object is not iterable, so you getTypeError: obj is not iterable. UseObject.keys(),Object.values()orObject.entries(). - Relying on
for...inorder. Integer keys come out in ascending order and the rest in insertion order, so a mixed object is not walked the way it looks. - Forgetting the prototype.
for...inclimbs the prototype chain and will show enumerable properties from there too. - Expecting
breakto work inforEach. It is not about these two loops as such, but it is often why they get chosen:forEachhas nobreakorcontinue, while both of these loops support them.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.