DEV Community

Cover image for Why you can't break a forEach loop | ByteSize JS
Jared
Jared

Posted on • Updated on

Why you can't break a forEach loop | ByteSize JS

I recently had a coding interview that involved evaluating one schema against another. The details of it aren't that important, but one thing that came out of it (in the middle of the interview) was that you can't break out of a forEach() loop. I had forgotten that little tidbit and it probably screwed up my chances of getting hired. After you read this, hopefully, you won't make the same mistake I did! Don't be like me.

https://media.giphy.com/media/nRkQduJDZ43bW/giphy.gif

Video Version

If you prefer to watch than read, check out the video version of this!

MDN Knows All

As noted by MDN:

There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool

That's some hardcore sass coming from the MDN docs. However, they are right, knowing which tool to choose is important.

Before we get too deep into why you can't break out of a forEach(), let's examine what a loop even is and where forEach() came from.

What is a Loop

A loop in programming solves a pretty common problem: I need to run the same code against all this data. Put simply, it is:

Repeating the same code over and over (on loop) until we reach a defined end state.

The Problem

For the sake of comparison, we're going to solve the same problem using the various loop types. Here is the problem:

Compare two arrays and see if the items in them are the same.

Here is the data we are going to compare:

    const jedis = ["Anakin","Luke"]
    const sith = ["Palpatine", "Anakin"]

We have two arrays, both with a couple of names. You'll probably notice that Anakin is both a Jedi and a Sith. This is a trivial example, however not far off from what I was tested on during my interview.

The Old A Way

What I don't want you to get from this article is that one loop is better than another. They all offer unique programming solutions and have a spot for specific use cases. The trick is knowing which one to use when.

Traditional For Loop

If you've ever taken any type of programming course, you've probably been exposed to our good friend the for loop. It has been a handy tool for programmers for a long time and is still useful today. Let's solve our problem using it.

// Our data again, for reference
const jedis = ["Anakin", "Luke"];
const sith = ["Palpatine", "Anakin"];
// start our loop, define our iterator variable
for (let i = 0; i < jedis.length; i++) {
  // create a variable we can reference
  const thisJedi = jedis[i];
  // see if the item in the array we are testing exists
  if (sith.includes(thisJedi)) {
    // If it does exist, then that jedi is also a sith
    console.log(`${thisJedi} is also a Sith`);
    // we can exit out
    break;
  }
  console.log(`${thisJedi} is not a Sith`);
}

The for loop offers a pretty handy way of exiting our code if it meets a condition we choose. This is immensely helpful when looping over a TON of data. It has been very helpful in solving some of the Project Euler problems, specifically this one.

The New Another Way

Among other things, forEach() was stamped in the spec in 2009 along with all the other goodness that was given to us in ES5. It serves as a handy method to write clean code that easily iterates over items in an array.

What is it doing?

A forEach() loop is a function that runs another function (callback) on each item in an array. We define what happens in that callback function. JS is nice enough to give us three parameters in that function:

  1. The item in the array
  2. The index of the item
  3. The whole array

Let's take a look at our problem using a forEach() loop instead. I've included all three parameters in the function, but we're only using the first, the item, which I'm naming jedi

    // We have to create a global state variable to keep track of what is happening
    let matching
    // loop over array 
    jedis.forEach((jedi,index,array) => {
      // check to see if jedi is in sith
      if(!sith.includes(jedi)) {
        // if it isn't, set global variable to false
        matching = false
      }
      // it keeps going...
    })
    console.log(matching) // false

If it makes more sense, you can refactor the callback function into a named function. I think it makes it a bit more readable. It also allows us to reuse this function wherever we want. Yay functional programming!

    let matching
    function isJediAlsoSith(jedi,index,array) {
      if(!sith.includes(jedi)) {
        matching = false
      }
    }
    jedis.forEach(isJediAlsoSith)

Our solution essentially does the same thing. The only difference is it keeps running until it reaches the end of the jedis array. For an array of such a small size, I doubt that it will make much of a performance difference.

But Why?

This finally brings us to the answer to our question, why can't we break out of a forEach() loop? It's because the loop is running that callback function over every item, so even if you write a return it's only returning on that instance of the function. It keeps going. In the case of the forEach() function, it doesn't do anything with the returned code. Be aware, that is not the case for some of the other Array Methods.

Additionally, because of this, break or continue are not valid statements.

Other Ways

There are quite a few different types of loops. They all have different purposes and I'd recommend looking into each one. You don't always need a forEach() loop.

https://media.giphy.com/media/tnwuVMx6n1VtK/giphy.gif

forEach() vs map()

Likely, the most common array methods that appear in tutorials are forEach() and map() . The biggest difference between the two is that map will return a new Array, while a forEach() won't.

Traditional Loops

while loop

Array Methods

Array.forEach()

Array.map()

Array.filter()

Array.reduce()

Array.reduceRight()

Array.every()

Array.some()

Array.indexOf()

Array.lastIndexOf()

Array.find()

Array.findIndex()

Iterable Object Loops (including Arrays)

for in

for of

This is the Way

Baby Yoda

As mentioned earlier by the incredibly sassy MDN docs, choosing the right tool is paramount to success. The number of options may seem a bit overwhelming at first, but I like to take the approach of: "if it works, it's the right tool."

Generally speaking, you can refactor your code to death, but then you're just wasting time you could be building stuff. In the case of my interview, I was using the right tool, the wrong way. Had I known remembered that you can't break out of a forEach loop, things probably would have turned out different 🤷🏼‍♂️.

If you have any additional info share, please drop it in the comments below!

As always, happy coding.

Plugs

Book

I'm writing a book about graphic design and how it relates to software development! If you're interested, sign up here for updates.

https://digitalnutt.substack.com/p/coming-soon?r=34slo&utm_campaign=post&utm_medium=web&utm_source=copy

Music

I also write music! Check it out here: Spotify | Youtube | Apple Music

https://open.spotify.com/track/4o9dZj5AF4nvPMnFFkqLhs

Support

If you like this article and want to see more, the best way to do that is to subscribe/follow me on here! If you are feeling gracious, you can buy me a coffee!

Oldest comments (12)

Collapse
 
gnunesleonardo profile image
Leonardo Gomes Nunes

I always remember that forEach() loop can't be break but i never know why, until now haha. Great article, thanks!

Collapse
 
codenutt profile image
Jared

Thanks!

Collapse
 
sunny_chan2012 profile image
Sunny Chan

But why they keep all these different versions of doing the same things? That is, forEach(), every() and some().

Collapse
 
folke profile image
Folke Lemaitre • Edited

You can use .every() instead and return false whenever you want to break. Return true to keep going

Collapse
 
codenutt profile image
Jared

Interesting! Thank you for your insight. I haven't used every much. Very handy!

Collapse
 
folke profile image
Folke Lemaitre

.some() is probably even better in most cases. Just return true whenever you need to break. No need to return anything when you want to keep going :)

Thread Thread
 
codenutt profile image
Jared

Sweet! May have to do a deep dive article into all the various loop types.

Collapse
 
monfernape profile image
Usman Khalil

This is so helpful

Collapse
 
codenutt profile image
Jared

Thanks! Any other topics you'd like me to tackle?

Collapse
 
kenbellows profile image
Ken Bellows • Edited

Awesome article! Minor nitpick though: Array.prototype.forEach() and its siblings map, filter, reduce, reduceRight, some, and every were all actually introduced back in 2009 with ES5!

I bring this up not for know-it-all points, but because it's important for browser compatibility. Most of these methods are available in browser versions going back 10+ years, which is super important and helpful if, like me, you work in an environment where you still need to maintain support for those browsers 😭

Collapse
 
codenutt profile image
Jared

Thank you for the correction! I'm the only one who edits these things, so I appreciate any and all feedback. I'll update it to reflect accurate info!

I feel you though, I have to support old browser's too

Collapse
 
szalonna profile image
Joe

I mean, you can if you really want: just throw sg from the callback.