DEV Community

Cover image for Interview: Can You Stop “forEach” in JavaScript?
Shahar Polak
Shahar Polak

Posted on • Updated on • Originally published at builtin.com

Interview: Can You Stop “forEach” in JavaScript?

The Saga of JavaScript's Most Persistent Loop

Interviewer: "Can you stop a relentless forEach loop in JavaScript?"

That question felt like being asked to stop the Earth from spinning. There I was, in an interview room that suddenly felt as hot as a server room with broken AC.

My answer? A hesitant, "No, that's against the laws of JavaScript nature."

Cue the sound of my job prospects plummeting.

Driven by a mix of desperation and a fleeting hope of redemption, I quizzed the interviewer, "Is there some kind of secret forEach kill-switch I'm not aware of?"

Before he could unveil the mysteries of JavaScript, I jumped in with what I knew.

Let's Spin a New Tale with Code

Here's a fun little array, just hanging out, minding its own business:

const friendlyArray = [5, 4, 3, 2, 1, 0, -1, -2, -3];

friendlyArray.forEach((number) => {
  if (number <= 0) {
    console.log("Stopping? Nope! Number:", number);
    return; // You might think this would stop it, but nope!
  }
  console.log("Number:", number);
});
Enter fullscreen mode Exit fullscreen mode

What does this do? It prints every number, and the return is as effective as a screen door on a submarine.

The interviewer, still holding onto the fantasy of stopping forEach, wasn't buying it.

Crafting a forEach Alternative

Time to roll up the sleeves and brew a new forEach concoction.

Array.prototype.forEachButWithStyle = function (callback) {
  if (typeof callback !== 'function') {
    throw new Error(`Hey, ${callback} is not a magical function!`);
  }

  for (let i = 0; i < this.length; i++) {
    callback(this[i], i, this);
    // Still no stopping - it's like a party that never ends!
  }
};
Enter fullscreen mode Exit fullscreen mode

Guess what? Even in this custom-built, shiny new forEachButWithStyle, there's no emergency exit. It's like a merry-go-round that's lost its off switch.

The Plot Twist: Stopping forEach with a throw Error

Just when you thought forEach was the marathon runner of JavaScript, never stopping for a break, I discovered a sneaky little trick. Picture this: a forEach loop, prancing around like it owns the place, and then suddenly, BAM! We throw an error, and it's like hitting the emergency stop button on a runaway carousel.

Here's the trick in action:

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

try {
  array.forEach((number) => {
    console.log("Number:", number);
    if (number === 3) {
      throw new Error("Oops! Stopping the loop.");
    }
  });
} catch (error) {
  console.log("Caught an error:", error.message);
}
Enter fullscreen mode Exit fullscreen mode

In this daring feat, when we hit the number 3, we throw an error. It's like setting off a flare in the middle of our forEach party. The loop screeches to a halt, and control is passed to the catch block. It's not the most graceful exit, more like jumping off a moving merry-go-round, but hey, it stops the loop!

But Wait, There's a Catch!

While this method does stop the loop, it's like using a sledgehammer to crack a nut. Throwing an error just to stop a loop is like cancelling the entire circus because you don't like clowns. It's a bit dramatic and can lead to other complications in your code.

So, there you have it. The forEach loop can indeed be stopped, but it's a bit like stopping a train with a boulder on the tracks. Effective, but not exactly recommended for everyday use.

Remember, with great power comes great responsibility. Use the throw Error method wisely, and maybe keep it as your secret JavaScript party trick.

The Conclusion

So, is it possible to stop a forEach loop in JavaScript? Well, technically, yes, but it's a bit like stopping a carousel with a roadblock. You can abruptly halt it with a throw Error, but it's more of a last-resort trick than a graceful solution.

It's like being at a party you can't leave, and the only way out is setting off the fire alarm – effective, but you'll definitely raise some eyebrows.

Remember, the next time someone asks you about stopping forEach, you can now give them a sly smile and share this whimsically mischievous tale. While forEach might seem like the Energizer Bunny of JavaScript, we've got a little trick up our sleeves to bring it to a standstill – just use it wisely!

Top comments (73)

Collapse
 
lexlohr profile image
Alex Lohr

There actually is a way to stop a forEach loop; it merely requires throwing an Error - you may augment it with data and catch it afterwards.

Collapse
 
thormeier profile image
Pascal Thormeier

Throwing errors (or exceptions in some languages) as a means of flow control is widely considered bad practice, though. Errors and exceptions should be used as such: An error is thrown if something unexpected happens. If you need to stop a for each loop, perhaps you shouldn't use a for each, but an iterator + while or a for loop instead.

Collapse
 
lexlohr profile image
Alex Lohr

You could also use an .every() loop instead and just return false if you want to break, but that does not answer the question how to stop a forEach loop.

In addition, unlike in other languages, throwing errors is not a bad practice in JS. On the contrary, especially with Promises, it is considered a regular means of flow control.

Thread Thread
 
thormeier profile image
Pascal Thormeier • Edited

Yes and no. A forEach loop can be broken by throwing errors, yes, but if you need to break it in your logic, you probably shouldn't use a forEach loop, because the name of it already states that you want to execute something for each element. Instead, filtering and then using a forEach is, in my opinion, more natural, because the intent is clearer. Or use an iterator with a while and break that.

I wasn't stating that throwing errors is bad, I was stating that throwing errors for flow control is bad. Errors indicate that you're not on the happy path anymore. If the errors are part of the happy path, they spark confusion. Of course promises can (and should) throw errors if something goes wrong, but if you need them to steer how something reacts to a promise, you could also resolve it with a different payload.

Errors and exceptions are always interpreted as "uh oh, something that shouldn't happen just happened." and therefore should only be used as such.

To come back to the original question: If an interviewer asked me how to break a forEach loop, I would ask them "why would I want to do that?"

Thread Thread
 
lexlohr profile image
Alex Lohr

Maybe the reason you need to interrupt the forEach loop is actually that you encountered an error?

And if I were the interviewer, my answer would be: "...to see if your knowledge of the language matches your CV."

Interviews are about your reaction, not about the resulting code. Would you ever want a fizz buzz in production? Me neither.

Thread Thread
 
polakshahar profile image
Shahar Polak

fizz buzz in production sounds like a dream 🤣

Thread Thread
 
maixuanhan profile image
Han Mai

Idk how you assess candidates but the ones can question the reason of trick questions like so are usually good. They can maintain the clean source code and avoid bad practices. I’m not sure if the ones can answer tricky techniques are the good one. They can be smart or they just learned the tricks somewhere but sure they will apply them into your company’s source code whenever they have a chance.

Thread Thread
 
lexlohr profile image
Alex Lohr

Not sure why you think this was a trick question. It is a good example of a question requiring knowledge and professionalism.

Someone lacking knowledge will answer that it is not possible to stop for each.
Those in the know will suggest throwing errors.
Professionals will also point out that this is only a good pattern to stop on errors and otherwise suggest a different pattern.

A technical interview is meant to gauge the capabilities of the interviewed. If you know of a better way to do this, please enlightened us.

Thread Thread
 
maixuanhan profile image
Han Mai • Edited

I explained the reason. I think your definition of “professionalism” is very different from the one that I know where the knowledge is not everything. Nobody can know all the programming techniques but they at least should know how to use some properly. “Requiring knowledge”, really? Your intention was wrong when you want to break “forEach”, in real life we don’t just put a regular function as a callback for you to catch the error. What if we put an async function, (because the iteration task requires to be awaited)? Throwing error is simply not working. I think breaking forEach is a codesmell and it will affect the maintainability of the source code.

For technical interview, there are also many aspects. For the programming languages, we usually ask about horizontal knowledge. For the essential knowledge, we can go vertically but still ask practical questions. JS and TS have lots of practical techniques to ask. Sometimes, we also ask trick questions but we assess candidates based on how they response to the questions, what they think about them and to have solutions/alternative solutions for them maybe.

Thread Thread
 
lexlohr profile image
Alex Lohr

I think your definition of “professionalism” is very different from the one that I know where the knowledge is not everything.

But that's exactly what I wrote. Knowledge is just the step from beginner to advanced. The step towards professionalism is wisdom, where you not only factor in your knowledge about systems and languages, but also the user and your team and the context in which you develop.

Your intention was wrong when you want to break “forEach”

You don't know the context in which the question would arise in real life and thus seem to have assumed a context that suited your opinions. But maybe the code using forEach was out of your control and you would either have to patch it (which will break easily and cause extra work) or throw an error to break the loop and handle it, while you file a ticket with the owner of the code to get another API that allows stopping the loop.

What if we put an async function, (because the iteration task requires to be awaited)?

Can you throw the error before you actually await something? That would still work; otherwise, you can store the promise in a variable and do the iteration manually while calling the API with a one-element-array for each item.

Thread Thread
 
thormeier profile image
Pascal Thormeier • Edited

I still think that if you need to break a forEach, that you probably shouldn't use a forEach, at leadt not in combination with another one. I can think of three cases that might throw errors: An invalid element in the list (for example null/undefined/a string/a negative number in a list of IDs), avalid value that produces an invalid one (for example Infinity after some multiplication, NaN, etc.) or if you need to call an external service/API for each element.

The first case can be solved by filtering the list first. If you validate the values beforehand, you can be sure that all the elements that the forEach is called for are actually valid, so no error is necessary.

The second case can be solved by using filter first and then mapping the value, or the other way around. You probably know the edge cases of that function (unit testing should take care of that), so you can filter out illegal elements beforehand.

The third case is a little trickier: Invalid elements can be filtered beforehand, thus reducing the total number of (probably expensive) HTTP calls. If the service is down, you'll likely encounter a ton of errors anyways. Ideally, you would map the values to promises, i.e. doing all calls in parallel, and have an await for all of them with a global catch. In an ideal world, you could batch-process large amounts of records with a single call anyway. Again, no forEach necessary, let alone breaking it.

I do agree, knowledge of the language is important, but what's more important, in my opinion, is to use that knowledge in the most efficient way.

EDIT: My partner (a teacher) just told me the crucial difference in opinion here: Is the interviewer testing knowledge ("can you break a forEach?") or competency ("let's see how they react to that question")? I deeply believe showing competency is more important in landing good jobs than showing knowledge, because competency grows from knowledge and, additionally, experience.

Thread Thread
 
lexlohr profile image
Alex Lohr

I still think that if you need to break a forEach, that you probably shouldn't use a forEach, at leadt not in combination with another one.

As I said already, if the forEach is in external code, your opinion could result in failure to deliver. That's also the reason why you don't want to hire developers who are too opinionated to accept pragmatic solutions that can be refactored in time, which will inevitably reduce the velocity of your team.

Is the interviewer testing knowledge ("can you break a forEach?") or competency ("let's see how they react to that question")?

I already told you it is both, just that you call it "competency" and I call it "professionalism".

Thread Thread
 
thormeier profile image
Pascal Thormeier • Edited

if the forEach is in external code

So how would I break the forEach, then? If it's external code, I can't alter it, except through forking/patching. And then I would restructure the code. I fail to see the scenario you're describing, what am I missing?

Regarding professionalism: You're absolutely right about that. But as mentioned, professionalism requires upfront knowledge. If you show professionalism, one can assume vast knowledge.

Thread Thread
 
lexlohr profile image
Alex Lohr

I should have elaborated more on the example:

// external code
externalItems.forEach(configurableCallback);

// internal code
try {
  callExternalAPI((item) => {
    if (!item.disallowed) {
      throw new Error('cancelling because of disallowed item.');
    }
  });
} catch(e) { ... }
Enter fullscreen mode Exit fullscreen mode

I hope that clarifies the scenario.

Thread Thread
 
thormeier profile image
Pascal Thormeier • Edited

Yeah, that does help indeed. In my opinion, in this specific case, throwing an error isn't explicitly added to only exit the forEach. The intent is different: Throwing the error exits the entire module code. If after the forEach an API call would've happened, that would not be executed either. Technically, throwing an error here does "break the forEach", but it also "jumps out of the module". If that's part of the requirements, that's fine, but if the API call afterwards should happen, throwing the error here wouldn't fulfil the requirements. I'm asking myself where the externalItems are coming from and if there's some way to filter them beforehand?

I'm very much enjoying this discussion, by the way, thank you for defending your point and challenging my arguments. It makes me think about things differently and reflect on some of my thinking patterns, which is always healthy. :)

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

You can actually throw stuff other than errors (and with this use case, doing that may even make it easier to differentiate an actual error from just exiting the loop - although a custom error object would likely be less frowned upon)

You're right though - it's pretty non-standard flow control, and probably not a good idea

Collapse
 
polakshahar profile image
Shahar Polak

You are right...

I have update the story 🙏

Collapse
 
dsaga profile image
Dusan Petkovic

Try catch around the foreach, and we throw an exception, thats what I thought, so this article is not entirely accurate

Collapse
 
polakshahar profile image
Shahar Polak

Hi Dusan, you are absolutely correct!

Therefore I have updated the last section to accommodate for it.
It's a solution that I do not like, but it it valid. 🙏

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

There's no good way to stop a forEach, as its name states it will loop for each item in the iterable.
wink wink

As people have said you can use try catch and throw to deal with that ungracefully (I personally would advice against doing so).

On the other side you can use a different looping tool for that job such as while or do...while, where you can break the loop whenever necessary, see below:

const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let i = 0;
Enter fullscreen mode Exit fullscreen mode
while(arr[i] !== undefined) {
  console.log(arr[i]);
  i++;
  if(arr[i] === 4) 
    break;
}
Enter fullscreen mode Exit fullscreen mode
do {
  console.log(arr[i]);
  i++;
  if(arr[i] === 4) 
    break; 
} while(arr[i] !== undefined);
Enter fullscreen mode Exit fullscreen mode

best regards

Collapse
 
mistval profile image
Randall • Edited

In addition to throwing an error there's at least one other way:

const friendlyArray = [5, 4, 3, 2, 1, 0, -1, -2, -3];

friendlyArray.forEach((number) => {
  if (number <= 0) {
    friendlyArray.length = 0;
  }
  console.log("Number:", number);
});
Enter fullscreen mode Exit fullscreen mode

This loop will only print the 6 numbers non-negative numbers.

But we should not do that except when we're trying to impress interviewers :)

Collapse
 
manchicken profile image
Mike Stemle

Modifying a list while iterating over it is its own taboo.

Collapse
 
mistval profile image
Randall

That's not the only thing taboo about this :)

Thread Thread
 
polakshahar profile image
Shahar Polak

🤣

Collapse
 
peter_schweitzerjr_8191 profile image
Peter Schweitzer, Jr

Even if it has the appearance of it, setting length=0 does not break forEach. It will still check for the existence of the indices for your negative numbers. See this jsfiddle.

Collapse
 
kylereeman profile image
KyleReemaN

process.exit() :S

Collapse
 
polakshahar profile image
Shahar Polak

WOW! that's true, but I don't know if I'd go that far haha

Collapse
 
yanai101 profile image
yanai

This is not js... this node implement

Collapse
 
codingjlu profile image
codingjlu

this exists the whole program, not just the loop

Collapse
 
kylereeman profile image
KyleReemaN

but it stops the loop! dude it's not that serious :)

Thread Thread
 
codingjlu profile image
codingjlu

I know, but usually when you early terminate a loop it's because you've encountered data that you want to be used elsewhere, ending the program doesn't allow that, making it pretty impractical. The error solution is bad enough, so I get that this isn't serious.

Collapse
 
yogski profile image
Yogi Saputro

Thank you for sharing this story. I wondered for a while when first reading the question.

We have continue and break in for loop. It also obeys await before moving to next element.

forEach is like a rebel.
That's probably what it does best. Being an unstoppable rebel.

Collapse
 
reyronald profile image
Ronald Rey • Edited
(function () {
    [1, 2, 3, 4, 5].forEach((i, _, arr) => {
        console.log(i)
        if (i > 2) {
            arr.length = 0
        }
    })

    console.log('Goodbye!')
})()
Enter fullscreen mode Exit fullscreen mode

Output:

1
2
3
Goodbye!
Enter fullscreen mode Exit fullscreen mode

👀

Collapse
 
peter_schweitzerjr_8191 profile image
Peter Schweitzer, Jr • Edited

Even if it has the appearance of it, setting length=0 does not break forEach. It will still check for the existence of the remaining indices. See this jsfiddle.

Collapse
 
alexus85 profile image
Alex Petrik

why not just turn off your computer?

Collapse
 
polakshahar profile image
Shahar Polak

🤣 always an option

Collapse
 
yebice profile image
Yan Bice

No need, just call alert() in the middle of the loop and the loop will stop until the modal is closed :)

Collapse
 
codingjlu profile image
codingjlu

Remember that plain old for-loop is always an option.

Collapse
 
aprates profile image
Antonio Prates • Edited

I guess you can throw an error, but you shouldn't. I suppose it's about semantics, so, you should either use a for-loop or rather filter first, something like:

[1, 2, 3, 4, 5]
  .filter(_ => _ < 3)
  .forEach(_ => console.log(_))
Enter fullscreen mode Exit fullscreen mode
Collapse
 
qdirks profile image
Quinn

The answer is no, not without throwing an error. Consider using a different method or approach if you want the iteration to stop, perhaps find or some methods, or a traditional loop.