Looping in JS - all the ways to iterate

Last updated on April 2021

Here is a list of everything I can think of relating to looping in JS.

There isn't much purpose to this post, I was just trying to find/think of all the ways to do anything related to loop/iterating.

I've ordered it by the most common/most useful at the start. Some are definitely loops, but as you scroll further down they're harder to say they're loops and more just using array-like functions.

I also start off with just normal for loops with arrays or for loops... but further down revisit it with other iterables, basic recursive functions etc.

Some are not typical ways of looping (e.g. is Array.some() considered a way to loop? Only technically...)

simple for loop

Most common is probably a for loop. This is the old classic for loop.

for(let i = 0; i < 5; i++) {
  console.log(i)
}
// outputs 0 - 4

You can make it an infinite loop:

for (let i = 0; ; i++) {
  if(i >= 5) break; // remove this for infinite loop
  console.log(i)
}
// outputs 0 -4
  • The keyword break will break out of the for loop.
  • the keyword continue will continue to the next iteration of the loop

for in loop

get the keys of an object:

const dict = {
    key1: 'val1',
    key2: 'val2',
    key3: 'val3'
}

for(const k in dict) {
    console.log(k);
}

// 'key1'
// 'key2'
// 'key3'

for of loop

Use a for of loop to get the values in an array

technically: get it from an iterable - more on these later

const vals = ['a', 'b', 'c', 'd'];

for(const v of vals) {
    console.log(v)
}

while loop

let i = 0;

while(i < 5) {
  console.log(i);
  i++;
}

// outputs 0-4

Can also do things like this:

const arr = [5, 10, 20];

while (arr.length > 0) {
    console.log(arr.shift());
}

do loop

Similar to a while loop, but when you need to always run it at least once. (The condition check is run after each iteration to see if it can continue)

let i = 999;

do{
  console.log(i)
}
while(i < 5)

// outputs just 999, and only runs the code inside do{} once

Array.map()

Use map to take an array, and transform each value to something else.

const vals = ['a', 'b', 'c', 'd'];

const newVals = vals.map(v => v.toUpperCase());

console.log(newVals) // [ 'A', 'B', 'C', 'D' ]

Array.forEach()

Use forEach in a very similar way to Array.map(), but use it when you don't need to create a new array.

(It returns undefined)

Useful if you want to run some function that has side effects.

const vals = ['a', 'b', 'c', 'd'];

vals.forEach(v => console.log(v))

// logs each letter

Array.reduce()

Take an array, reduce it down to one value.

(That value can be another array).

There are 2 main args to the function - acc, cur (accumulative, current).

const vals = [50, 100, 200]

// 2 args: 
// - a callback (acc, cur, index)
// - optional 2nd arg which is the initial value. In most cases you should set initial value. Otherwise it uses the first item in the array
const sum = vals.reduce((acc, cur) => acc + cur, 0)
const sum2 = vals.reduce((acc, cur) => acc + cur) // don't need initial value here for things like sum

console.log(sum); // 350
console.log(sum2); // 350

// this would be easier with .map()
const newVals = vals.reduce((acc, cur) => {
  return [...acc, cur * 2]
}, []) // need 2nd param here to build up a new array
console.log(newVals) // [ 100, 200, 400 ]


// this would be easier with .filter()
const onlyLargeNumbers = vals.reduce((acc, cur) => {
  if(cur >= 100) return [...acc, cur]
  return acc
}, []) 
console.log(onlyLargeNumbers) // [ 100, 200 ]

There is also .reduceRight() which works in reverse order (starting at the end of the array instead of at the start).

Array.filter()

You use filter to take in an array, and return a new array with certain items removed.

const vals = [50, 100, 200]

const largeNums = vals.filter(num => num >= 100)
console.log(largeNums); // [ 100, 200 ]

Array.every()

Use .every() to return a boolean if everything in an array matches a condition.

const vals = [2, 4, 8, 10, 12,]

const allEven = vals.every(num => num % 2 === 0)
console.log(allEven) // true

const allLargerThan5 = vals.every(num => num > 5)
console.log(allLargerThan5) // false

Array.some()

Use Array.some() to return true if the function you pass returns true for at least one

const vals = [2, 4, 8, 10, 12,]

const atLeastOneEven = vals.some(num => num % 2 === 0)
console.log(atLeastOneEven) // true

const atLeastOneLargerThan5 = vals.some(num => num > 5)
console.log(atLeastOneLargerThan5) // true

Array.includes()

Pass in a value and it returns true/false if an array contains that value.

It is similar to .find(), but in .find() you pass a function that is run on every object. With .includes() it just does a equality check

const animals = ['snake', 'horse', 'cat'];

console.log(animals.includes('snake')); // true
console.log(animals.includes('dog')); // false

// note:
const animals2 = [
    {type: 'snake'},
    {type: 'horse'}
]
console.log(animals.includes({type: 'snake'})) // false, as it compares the objects with are different

Array.indexOf()

Returns the index in an array of an object/value, or -1 if not found.

It returns the first index where it finds it

This is similar to .findIndex(), but in findIndex() you pass in a function that returns true/false. In .indexOf() it just does an equality check.

const animals = ['snake', 'horse', 'cat'];

console.log(animals.indexOf('cat')); // 2
console.log(animals.indexOf('dog')); // -1

// note:
const animals2 = [
    {type: 'snake'},
    {type: 'horse'}
]
console.log(animals.indexOf({type: 'snake'})) // -1, as it compares the objects with are different

Array.lastIndexOf()

Use .lastIndexOf() just like .indexOf but instead of starting from the start of the index, it starts at the end

const animals = ['snake', 'horse', 'cat', 'horse', 'dog'];

console.log(animals.indexOf('horse')); // 1
console.log(animals.lastIndexOf('horse')); // 3

Array.find()

Find first item in an array that matches a condition

const vals = [2, 4, 8, 10, 12,]

const firstBiggerThan5 = vals.find(num => num > 5)
console.log(firstBiggerThan5) // 8

const firstBiggerThan50 = vals.find(num => num > 50)
console.log(firstBiggerThan50) // undefined

Array.findIndex()

Similar to .find() but returns the index (or -1 if not found)

const vals = [2, 4, 8, 10, 12,]

const firstBiggerThan5 = vals.findIndex(num => num > 5)
console.log(firstBiggerThan5) // 2

const firstBiggerThan50 = vals.findIndex(num => num > 50)
console.log(firstBiggerThan50) // -1

Array.flat()

Use .flat() to flatten an array of arrays.

const allCases = [
    ['hi', 'HI', 'Hi'],
    ['bye', 'BYE', 'By']
]

console.log(allCases.flat())
// [ 'hi', 'HI', 'Hi', 'bye', 'BYE', 'By' ]

Array.flatMap()

Similar to Array.map() except it will flatten it if it contains arrays

const words = ['hi', 'bye'];

const allCases = words.flatMap(plainWord => [plainWord, plainWord.toUpperCase(), plainWord.toLowerCase()])

console.log(allCases)

// [ 'hi', 'HI', 'hi', 'bye', 'BYE', 'bye' ]

Array.sort()

This isn't a normal way to iterate over an array... but it does. (and will read items in the array more than once as it sorts).

const vals = [123, 456, 1, 2, 3, 1000, 2000, 1, 1, 3]

// .sort() sorts in place and mutates original. good practice to
// always leave original alone, so we start with a spread...
const sorted = [...vals].sort((a, b) => {
    console.log(`Comparing ${a} <> ${b}`)
    if(a > b) return 1
    if(a < b) return -1
    return 0
})

console.log(sorted)

/*
'Comparing 456 <> 123'
'Comparing 1 <> 456'
'Comparing 1 <> 456'
'Comparing 1 <> 123'
'Comparing 2 <> 123'
 // truncated ...
'Comparing 1 <> 2'
'Comparing 1 <> 1'
'Comparing 3 <> 3'
'Comparing 3 <> 1000'
'Comparing 3 <> 456'
'Comparing 3 <> 123'

Final output
[ 1, 1, 1, 2, 3, 3, 123, 456, 1000, 2000 ]
*/

In chrome it uses timsort which is O(n log n).

Array.entries()

I rarely see this in use but it is nice if you want key/value pairs for every i/itemndex in an array:

const arrayOfNums = [5, 10, 20];

for (let [index, value] of arrayOfNums.entries()) {
    console.log(index, value);
}
/*
0 5
1 10
2 20
*/

Array.keys()

Gets keys from an array.

const arrayOfNums = [5, 10, 20];

const keys = arrayOfNums.keys()
console.log([...keys]) // [ 0, 1, 2 ]

note the difference between Array.keys() and Object.keys() on a sparse array:

// comparison to Object.keys() on an array:
const arr = ["a", , "c"]; // note the `, ,` to generate a sparse array
const sparseKeys = Object.keys(arr);
const denseKeys = [...arr.keys()];
console.log(sparseKeys); // ['0', '2']
console.log(denseKeys); // [0, 1, 2]

Array.from()

You can create a new array, and use .from() to loop through the new items in that array.

const newArray = Array.from({length: 5}, (x, i) => i)
console.log(newArray) // [ 0, 1, 2, 3, 4 ]

const newArrayOfAs = Array.from({length: 5}, () => 'a')
console.log(newArrayOfAs) // [ 'a', 'a', 'a', 'a', 'a' ]

Looping over objects

You can use some functions on Object to get keys, values and entries to then loop over those.

const myObject = {
    k1: 'v1',
    k2: 'v2'
}

const keys = Object.keys(myObject);
keys.forEach(k => console.log(k)) // 'k1', 'k2'
keys.forEach(k => console.log(myObject[k])); // 'k1', 'k2'

const values = Object.values(myObject)
values.forEach(v => console.log(v)) // 'v1', 'v2'

const entries = Object.entries(myObject)
console.log(entries[0]); // ['k1', 'v1']
entries.forEach(kv => {
    console.log(`myObject[${kv[0]}] has value of ${kv[1]}`)
})
// 'myObject[k1] has value of v1'
// 'myObject[k2] has value of v2'

Loop over generators

const generator = function* () {
    console.log("starting")
    yield 'a';
    console.log("between a-b")
    yield 'b';
    console.log("between b-c")
    yield 'c';
    console.log("end")
}

for (const val of generator()) {
    console.log(val)
}

/*
'starting'
'a'
'between a-b'
'b'
'between b-c'
'c'
'end'
*/

You can also spread it to get an array (to use things like .map())

const generator = function* () {
    yield 'a';
    yield 'b';
    yield 'c';
}

const asArray = [...generator()]
asArray.forEach(v => console.log(v))

/*
'a'
'b'
'c'
*/

Loop over iterables

Normally you cannot loop over plain old javascript objects like this:

const myObject = {
  k1: 'v1',
  k2: 'v2'
}

for(const val of myObject){
  console.log(val) // TypeError: myObject is not iterable
}

But if you set the special property Symbol.iterator, then you can!

const myObject = {
    *[Symbol.iterator]() {
        yield this.k1
        yield this.k2
    },

    k1: 'v1',
    k2: 'v2'
}

for(const val of myObject){
    console.log(val)
}

/*
outputs:
'v1'
'v2'
 */

or combine it with something like Object.keys(this):

const myObject = {
    *[Symbol.iterator]() {
        const allKeys = Object.keys(this)
        for(const key of allKeys) {
            yield this[key]
        }
    },

    k1: 'v1',
    k2: 'v2'
}

for(const val of myObject){
    console.log(val); // 'v1', 'v2'
}

Spreading

This isn't really iterating, but if an object is iterable then you can spread it.

const hello = 'hey';
const split = [...hello];
console.log(split); // [ 'h', 'e', 'y' ]

const ages = [10, 20, 30, 40];
const agesClone = [...ages]
console.log(ages, agesClone) // [ 10, 20, 30, 40 ] [ 10, 20, 30, 40 ]
console.log(ages === agesClone) // false
console.log(JSON.stringify(ages) === JSON.stringify(agesClone)) // true

const agesSet = new Set([1,2,3]);
agesSet.add(4)
const asArray = [...agesSet]
console.log(asArray) // [ 1, 2, 3, 4 ]

Splitting strings

Similar to above on the string, you can use .split() to turn a string to an array. This is a stretch calling it a loop/iterating over it, but maybe it counts.

const hi = 'hello'

const letters = hi.split('')
console.log(letters) // [ 'h', 'e', 'l', 'l', 'o' ]

Loop over DOM elements

If using JS in a browser, there are some things that you can loop over. They often only have .forEach() but they're iterable so you can use the spread operator to get a proper array.

document.querySelectorAll('div').forEach(el => console.log(el))

const asArray = [...document.querySelectorAll('div')];
const texts = asArray.map(el => el.textContent)

const inputs = document.querySelectorAll('input');
for (const input of inputs) {
    console.log(input.getAttribute('value')
}
  • querySelectorAll() returns a NodeList
  • NodeList has .forEach(), .entries(), .keys(), .values(), and a .length property but it is not an array.
  • Convert to an array by spreading [... result] or Array.from(result)
  • querySelectorAll() returns a static NodeList. There are also live NodeLists, which means that changes in the DOM automatically update the collection.

Loop over Sets

Similar to DOM elements, Sets are iterable and have some functions like .forEach... but you can spread it to get a normal array

const words = new Set()
words.add('hi')
words.add('bye')

words.forEach(w => console.log(w)); // 'hi', 'bye'

const asArray = [...words]
const upperCase = asArray.map(w => w.toUpperCase())
console.log(upperCase) // [ 'HI', 'BYE' ]

Loop with recursion

This is cheating a bit, but you can iterate through items with recursion

function countFromTo(start, max, cur = start) {
    if(cur > max) return
    
    console.log(cur)
    countFromTo(start, max, cur + 1)
}

countFromTo(10, 15) // 10 11 12 13 14 15

Or iterate through an array using recursion:

function rec(vals) {
  if(!vals.length) return
  const [first, ...rest] = vals
  console.log(first);
  
  rec(rest)
}

rec(['a', 'b', 'c'])
// outputs: 
// 'a'
// 'b'
// 'c'

const vals2 = ['a', 'b', 'c']
function rec2(index) {
    if(index === vals2.length ) return;
    console.log(vals2[index])
    
    rec2(index+1)
}

rec2(0)
// 'a', 'b', 'c'

Convert array to a string

Not sure this really counts as a loop, but maybe...

const arr = [1, 2, 3, 4, 5];

const asStr = arr.toString()
console.log(asStr)

const asStr2 = arr.join()
console.log(asStr2)

Use .apply() to use all items in an array as arguments for a function

You can call a function, and pass in an array which is used for each argument to call that function. (And sets this - but in my demo I ignore that)

This barely counts as a way to loop, but it does use arrays...

const arr = [5, 10, 20];

function printThreeVars(a, b, c) {
  console.log(a, b, c);
}

printThreeVars.apply(null, arr);
// 5 10 20

Loop with recursive timeout

This is really just a recursive function, very similar to the previous recursive idea to iterate through multiple calls...

// loop with setTimeout 

let max = 5
function processNumber(i) {
  console.log(i)
  if(i > max) return;
  setTimeout(() => processNumber(i + 1), 0)
}

processNumber(0)

Math.min and other functions that you can spread an array into

Are these looping/iterating? Interally they are as they go through all arguments passed in... I don't think it really counts as a loop/iterator though.

const vals = [1, 4, 5, 9]

const max = Math.max(...vals)
const min = Math.min(...vals)
console.log({max, min}); // { max: 9, min: 1 }

Looping through DOM array-like objects

I've mentioned NodeList which is array-like.

There are other array-like objects, so they are easy to loop/iterate over. Most are part of the web api.

Here is a list of some of them.

strings

  • strings are array-like.
  • They have .length, you can loop over them, you can spread them

HTMLCollection

  • HTMLCollection are lists of elements in the DOM.
  • You can get them with calls to functions like document.getElementsByClassName('your-class')

FileList

  • You get a FileList when getting an input that has type='file'
  • It has .length so you can do a normal for loop
  • For example if you have <input id="fileItem" type="file" />, then document.getElementById('fileItem') is a FileList, which is array-like

DOMTokenList

Get this when looking at classlist on an element.

For example:

document.querySelector('body').classList.forEach(c => console.log(c))

TypedArray

(not DOM/web API only)

Things like Uint8Array (and related) and in NodeJS Buffer, extend TypedArray.

You can call things like .forEach on these:

const nums = new Uint8Array([10, 20, 30]);

nums.forEach((item) => console.log(item))

Ok, that is everything I could come up with related to looping or iterating in JS. Get in touch if you can think of anything I missed.

© 2019-2023 a5h.dev.
All Rights Reserved. Use information found on my site at your own risk.