Get loop counter/index using for…of syntax in JavaScript
Asked Answered
H

13

686

I understand that the basic for...of syntax in JavaScript looks like this:

for (let obj of myArray) {
  // ...
}

But how do I get the loop counter/index when iterating with this syntax?

(With the same question applying to for...in notation for iterating over object property names)

I know I can use an explicit loop counter like:

for (let i = 0; i < myArray.length; i++) {
  const obj = myArray[i];
  console.log(i);
}

Or manually track the index outside of the loop:

let i = 0;
for (let obj of myArray) {
  console.log(i);
  i++;
}

But I would rather use the simpler for...of loop, I think they look better and make more sense.

As an example of a language that lets you do this, in Python it's as easy as:

for i, obj in enumerate(my_array):
    print(i)
Hospitium answered 16/4, 2012 at 18:47 Comment(2)
Don't use for...in for arrays. And anyways, it iterates over the property names, not the values of the properties.Melodeemelodeon
It's an array, not an object, right? So, alert(obj)?Wauters
N
1118

for…in iterates over property names, not values, and does so in an unspecified order (yes, even after ES6). You shouldn’t use it to iterate over arrays. For them, there’s ES5’s forEach method that passes both the value and the index to the function you give it:

var myArray = [123, 15, 187, 32];

myArray.forEach(function (value, i) {
    console.log('%d: %s', i, value);
});

// Outputs:
// 0: 123
// 1: 15
// 2: 187
// 3: 32

Or ES6’s Array.prototype.entries, which now has support across current browser versions:

for (const [i, value] of myArray.entries()) {
    console.log('%d: %s', i, value);
}

For iterables in general (where you would use a for…of loop rather than a for…in), there’s nothing built-in, however:

function* enumerate(iterable) {
    let i = 0;

    for (const x of iterable) {
        yield [i, x];
        i++;
    }
}

for (const [i, obj] of enumerate(myArray)) {
    console.log(i, obj);
}

If you actually did mean for…in – enumerating properties – you would need an additional counter. Object.keys(obj).forEach could work, but it only includes own properties; for…in includes enumerable properties anywhere on the prototype chain.

Newscast answered 16/4, 2012 at 18:49 Comment(15)
Actually, obj will be the array index, but there is no guarantee that it is in order and that it won't include other property names.Melodeemelodeon
don't you need to do var i = 0, because lets are unchanging?Freddie
@quantumpotato: lets are vars with block scope. consts are unchanging.Newscast
I did not know this was possible: myArray.forEach(function (value, *i*) (emphasis mine). This is perfect. I knew there had to be a better way than that myArray.indexOf(value) garbage I've been doing. +1.Lunnete
In the example above, 'i' which should be the index returns undefined, please help.Beattie
@Beattie In which example?Newscast
I have sorted my self now. (The mistake I'd made was following your solution whilst still using the ECMA arrow and without the keyword, "function". Thanks Ry.Beattie
stupid question but what does %d and %s actually stand for, or could they be any letter I want them to be?Calf
@klewis: %d formats an integer and %s formats a string. They’re inspired by printf. A spec is in progress at console.spec.whatwg.org/#formatter.Newscast
Note the other answer below in reference to arrow functions... But the above does work too.Ferrol
The downside of forEach is that await inside is scoped to the function parameter, not the outer scope. So if you want to await inside the loop, you probably want to use .entries().Jura
Which one is faster in Node 18?Hurlburt
@Antoine: It’s usually close enough not to matter in modern V8; I essentially always use for of. You can benchmark your own use case to get concrete numbers.Newscast
Note: you should take special precautions if you're using an async callback with something like foreach since it would run the loops in parallel if you were hoping to run in serial.Immuno
From MDN: "The traversal order, as of modern ECMAScript specification, is well-defined and consistent across implementations. Within each component of the prototype chain, all non-negative integer keys (those that can be array indices) will be traversed first in ascending order by value, then other string keys in ascending chronological order of property creation." developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Hysterectomy
P
467

In ES6, it is good to use a for... of loop. You can get index in for... of like this

for (let [index, val] of array.entries()) {
  // your code goes here    
}

Note that Array.entries() returns an iterator, which is what allows it to work in the for-of loop; don't confuse this with Object.entries(), which returns an array of key-value pairs.

Pastorale answered 19/12, 2016 at 11:17 Comment(10)
I think this solution is better than the forEach one... It uses the nomral for...of loop syntax, and you don't have to use a separate function. In other words, it's syntactically better. The OP seems to have wanted this.Eidson
entries() is returning an empty object: {}. Any idea why that would be? My array is an Array of Objects.Structuralism
@JoshuaPinter try Object.entries(array) instead of array.entries()Sydneysydnor
It's supposed to do that, Joshua - the object is an iterator, an object with a next() method that will return subsequent entries in the array each time it is called. There's no (visible) data in it; you get the data in the underlying object by calling next(), which for-of does behind the scenes. cc @SydneysydnorCoffin
woah, I take it back. I was using a repl and array.entries() always returns {} but if you actually use the code above, using for ... of array.entries() it works just fine. Sorry about that.Sydneysydnor
Also this allows await to work in sequence, whereas forEach does not.Srinagar
Be aware this only works for Array. for...of works on any Iterable. You might have to convert to Array with for (const [index, value] of [...array].entries()). Iterables include HTMLCollection NodeList, and other DOM classes.Constantine
nicely done! ...Glasgo
Why use let instead of const?Pentatomic
I agree with @Ben. The values don't change in scope so should be const, not let.Twaddle
S
55

How about this

let numbers = [1,2,3,4,5]
numbers.forEach((number, index) => console.log(`${index}:${number}`))

Where array.forEach this method has an index parameter which is the index of the current element being processed in the array.

Setscrew answered 10/2, 2018 at 4:17 Comment(3)
The chosen answer was posted 6 years before this one and has the same thing already in it...Peeples
Foreach is not good for optimization, since break is not available.Nemertean
@smartworld-dm I do agree with this but there is the simple return statement.Latchkey
W
21

Solution for small array collections:

for (var obj in arr) {
    var i = Object.keys(arr).indexOf(obj);
}

arr - ARRAY, obj - KEY of current element, i - COUNTER/INDEX

Notice: Method keys() is not available for IE version <9, you should use Polyfill code. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

Witherite answered 12/1, 2015 at 9:36 Comment(17)
I'd suggest: use a counter instead, increment it in loop.Mannerism
Adding on to mayankcpdixit, use a counter instead because indexOf could have a negative performance impact.Doughman
The larger the object, the slower this will get. This does not scale.Sinful
This is kind of pointlessly slow and complicated because var i = 0; and i++; is shorter and more efficient. Plus it doesn't work for enumerable properties that aren't own properties.Newscast
@Mannerism Incrementing a counter won't work in for..of loops that modify the array during iteration.Theatricals
@Ryan var i = 0; and i++; won't work in loops that modify the array during iteration. @rushUp's answer is superb, because the index will be the original index of each item before iteration, regardless of the array being modified during iteration.Theatricals
@trusktr: Why are you replying to a comment about this answer, which iterates over an array in ϴ(n²) time?Newscast
@Theatricals yes! but for...of is still relatively new. If you're using for...in, counter would be a good coice.Mannerism
@Ryan If modifying the collection during iteration isn't required, then yeah, a regular for loop is a good way to make it faster. :}Theatricals
@trusktr: And if it is required… you should still not use this. Just alter the counter when you alter the collection. If it doesn’t have to be in-place, do a nice functional transformation instead.Newscast
@Ryan That makes code harder to read. I would say it depends on the use case. If you are using it for something that the end user will not perceive performance-wise, then use it. For 60fps graphics? Maybe not.Theatricals
@trusktr: Which option to pick depends on the use case. Whether to pick this option does not. There is no reason to get a new Object.keys and call indexOf on it for each item, ever. It takes time proportional to n² instead of n and is even less readable than a regular explicit loop.Newscast
This has horrible performance characteristics, and should not be suggested ever.Disrepute
And it won't work it some value are duplicated in the arrayArable
@JaysonMinard To be fair, the author did specify "small arrays". Performance is usually not a concern there.Skricki
why learn the non performant way ever when the better options are just as easy? this is how people learn bad habits and don't know they are bad habits.Disrepute
How on Earth did anybody upvote this?Tricolor
M
17

For-in-loops iterate over properties of an Object. Don't use them for Arrays, even if they sometimes work.

Object properties then have no index, they are all equal and not required to be run through in a determined order. If you want to count properties, you will have to set up the extra counter (as you did in your first example).

loop over an Array:

var a = [];
for (var i=0; i<a.length; i++) {
    i // is the index
    a[i] // is the item
}

loop over an Object:

var o = {};
for (var prop in o) {
    prop // is the property name
    o[prop] // is the property value - the item
}
Meara answered 16/4, 2012 at 18:49 Comment(7)
Never do (var i=0; i<a.length; i++) as is wasted resources. Use (var i=0, var len = a.length; i<len; i++)Blamed
@FelixSanz: Waste resources? No way. That is a premature micro-optimisation that is hardly ever necessary, and var i=0; i<a.length; i++) is the standard loop pattern that is optimised by every decent javascript engine anyway.Meara
best practices are best practices!Blamed
@FelixSanz: Yes, and var i=0; i<a.length; i++ is the best practise.Meara
KISS. If you write loops where you really need this you either are doing something wrong, or you have a better argument for its necessity than "best practise". Yes, it is a standard practise, but not for generic performance optimisation, but only for micro-optimisation.Meara
KISS is just a principle. You are not proving it applies here, i did. so i won't reply anymore. do whatever you want.Blamed
KISS applies everywhere. Premature optimisation is an anti-practise.Meara
H
11

As others have said, you shouldn't be using for..in to iterate over an array.

for ( var i = 0, len = myArray.length; i < len; i++ ) { ... }

If you want cleaner syntax, you could use forEach:

myArray.forEach( function ( val, i ) { ... } );

If you want to use this method, make sure that you include the ES5 shim to add support for older browsers.

Hawfinch answered 16/4, 2012 at 18:55 Comment(0)
A
7

Answer Given by rushUp Is correct but this will be more convenient

for (let [index, val] of array.entries() || []) {
   // your code goes here    
}
Arcuation answered 13/4, 2020 at 12:29 Comment(4)
|| [] is unnecessary and will never be used; array.entries() is always truthy.Newscast
[index, val] never works for me, it says "undefined"Quaker
can you share your array ?Arcuation
Why repeat an existing answer? Downvoted.Tricolor
S
2

Here's a function eachWithIndex that works with anything iterable.

You could also write a similar function eachWithKey that works with objets using for...in.

// example generator (returns an iterator that can only be iterated once)
function* eachFromTo(start, end) { for (let i = start; i <= end; i++) yield i }

// convers an iterable to an array (potential infinite loop)
function eachToArray(iterable) {
    const result = []
    for (const val of iterable) result.push(val)
    return result
}

// yields every value and index of an iterable (array, generator, ...)
function* eachWithIndex(iterable) {
    const shared = new Array(2)
    shared[1] = 0
    for (shared[0] of iterable) {
        yield shared
        shared[1]++
    }
}

console.log('iterate values and indexes from a generator')
for (const [val, i] of eachWithIndex(eachFromTo(10, 13))) console.log(val, i)

console.log('create an array')
const anArray = eachToArray(eachFromTo(10, 13))
console.log(anArray)

console.log('iterate values and indexes from an array')
for (const [val, i] of eachWithIndex(anArray)) console.log(val, i)

The good thing with generators is that they are lazy and can take another generator's result as an argument.

Sixtyfourmo answered 24/1, 2019 at 14:13 Comment(0)
S
2

That's my version of a composite iterator that yields an index and any passed generator function's value with an example of (slow) prime search:

const eachWithIndex = (iterable) => {
  return {
    *[Symbol.iterator]() {
      let i = 0
      for(let val of iterable) {
        i++
          yield [i, val]
      }
    }
  }

}

const isPrime = (n) => {
  for (i = 2; i < Math.floor(Math.sqrt(n) + 1); i++) {
    if (n % i == 0) {
      return false
    }
  }
  return true
}

let primes = {
  *[Symbol.iterator]() {
    let candidate = 2
    while (true) {
      if (isPrime(candidate)) yield candidate
        candidate++
    }
  }
}

for (const [i, prime] of eachWithIndex(primes)) {
  console.log(i, prime)
  if (i === 100) break
}
Stadiometer answered 4/3, 2019 at 11:9 Comment(2)
Why do you have a function eachWithIndex[Symbol.iterator] instead of just a function eachWithIndex? eachWithIndex doesn’t satisfy the iterable interface, which is the whole point of Symbol.iterator.Newscast
@Newscast Good catch, changed eachWithIndex to accept iterable and return a closured composite iterable.Stadiometer
R
2

On top of the very good answers everyone posted I want to add that the most performant solution is the ES6 entries. It seems contraintuitive for many devs here, so I created this perf benchamrk.

enter image description here

It's ~6 times faster. Mainly because doesn't need to: a) access the array more than once and, b) cast the index.

Reiner answered 22/4, 2020 at 9:17 Comment(4)
I've got to say you are not comparing apple to apple in the above test case. In classic, extra const v is defined plus the unnecessary type conversion Number(i) all led to its overhead. By removing those bits, my result shows the contrary: classic is 4 times faster. Please check the updated version hereKaete
@Kaete Your link is deadHornswoggle
@javadba, That's because jsperf is down. I'll create a new answerKaete
Saying it’s the “most performant solution” based on a benchmark that only includes one other approach (that also happens to be wrong) is pretty misleading. How about comparing it against the top answers?Newscast
S
0

One answer that seems to be missing is that in Python, the enumeration is a function. So: you could just do the same in JavaScript and then destructure during your for...of loop:

// Stick this in any random utils.js that you can import from
function enumerate(arr) {
  return arr.map((element, i) => [i, element]);
}

const myArray = ['a','b','c','d','e'];

// And then loop over *that* instead of the plain array:
for (let [i, element] of enumerate(myArray)) {
  console.log(i, element);
}

But I'd probably go with a map instead, of course:

myArray.forEach((element, i) => {
  console.log(i, element);
});
Sanatory answered 15/3 at 21:22 Comment(0)
P
-1
// this loop is used in advanced javascript
//For Example I have an array:
let array = [1,2,3,4,5];
1) for(let key in array){
      console.log(key);//this shows index of array {Result: 0,1,2,3,4}
      console.log(array[key]);//this show values of array {Result: 1,2,3,4,5}
   }
//Hopefully, You will quickly understand;
Phippen answered 25/9, 2022 at 20:43 Comment(0)
C
-5

To use for..of loop on array and retrieve index you can you use array1.indexOf(element) which will return the index value of an element in the loop. You can return both the index and the value using this method.

array1 = ['a', 'b', 'c']
for (element of array1) {
    console.log(array1.indexOf(element), element) // 0 a 1 b 2 c
}

As mentionned in comments, this will return false index when the array contains non uniques values. (considering arr = ['a', 'b', 'c', 'a'], index of arr[3] will return 0 instead of 3) so this should be better suited for Sets than for Arrays.

Chenab answered 23/6, 2020 at 7:40 Comment(3)
Please explain your code a little as to what it does rather than adding snippets only. Plus it doesn't exactly answer the question. The question is rather about objects and taking an example of an array would perhaps be oversimplification. (From Review)Artisan
useful with iterable objects, thanks pal, my_list.indexOf(element), though lambda expressions forEach is very useful.Gratuity
This is unnecessarily quadratic and will produce incorrect results when the array contains duplicates.Newscast

© 2022 - 2024 — McMap. All rights reserved.