DEV Community

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

Interview: Can You Stop “forEach” in JavaScript?

Shahar Polak on December 26, 2023

The Saga of JavaScript's Most Persistent Loop Interviewer: "Can you stop a relentless forEach loop in JavaScript?" That question felt like being ...
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.

Collapse
 
shameel profile image
Shameel Uddin

Thanks for bringing it up! =)

Collapse
 
yousufrana profile image
Yousuf Rana

That's right, thank you.

Collapse
 
abidullah786 profile image
ABIDULLAH786 • Edited

and what about this one

    array = [1,2,3,4,5,6]
    array.forEach(function loop(number){
        if(loop.stop){ return; }

        if(number==3){ loop.stop = true; }
         console.log("Number: "+ number)
    });
Enter fullscreen mode Exit fullscreen mode
Collapse
 
johan_dahl_c911f493467445 profile image
Johan Dahl

Not really stopping the forEach. Just early return of all after the 3. If Array has 1 billion elements it would still go thru them.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
lexlohr profile image
Alex Lohr

I prefer using every, as that means you have to return false to break. It makes for a more understandable pattern.

Collapse
 
proteusiq profile image
Prayson Wilfred Daniel

Wow! I thought I was a good storyteller. You are brilliant. 🤗 That is an incredibly beautiful read.

Collapse
 
pinotattari profile image
Riccardo Bernardini

I am not an expert of JS (used every now and then when the need arises), so sorry for the naive question... From this I guess that there is no break statement in JS or that, at least, it does not work with forEach. Why? As I understand that forEach means loop over all members, I imagine you can easily imagine situations where a premature loop ending is legit.

Collapse
 
bwca profile image
Volodymyr Yepishev

It is a red flag question. forEach is not a loop and using exceptions to interrupt it is a bad practice.

Collapse
 
nasheomirro profile image
Nashe Omirro • Edited

err.. I mean for the part where we create our own array method we could say that if the callback returned some sign to keep going or to stop then it should be possible:

  // ...
  for (let i = 0; i < this.length; i++) {
    const isPartyOver = callback(this[i], i, this);
    if (isPartyOver === "yes") break;
  }
  // ...
Enter fullscreen mode Exit fullscreen mode

we could even wrap the callback in a try block as well to semi-gracefully not have to handle the error.

Collapse
 
santiael profile image
Rafael Santiago

Just for fun, I made a custom-build that works by changing any return into the function to a value that triggers the break in a for loop.

Array.prototype.breakableForEach = function (callback) {
    const STOP = Symbol('breakableForEach.stop');
    const stopCallback = createStopCallback(callback);

    for (let i = 0; i < this.length; i++) {
        if (stopCallback(STOP, this[i], i, this) === STOP) break;
    }

    function createStopCallback(func) {
        const funcString = func.toString();

        const params = /\(([^)]+)\)/.exec(funcString)?.[1].split(',').map(param => param.trim()) || [];
        const body = /{([\s\S]*)}/.exec(funcString)?.[1].trim().replace('return', 'return __stop__;') || '';

        return Function('__stop__', ...params, body);
    }
}

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

array.breakableForEach((n) => {
    if(n === 3) return;
    console.log(n);
})
Enter fullscreen mode Exit fullscreen mode

Output:

1
2
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lnahrf profile image
Lev Nahar

forEach is the most inferior for loop.

Collapse
 
polakshahar profile image
Shahar Polak

Lev, but it is easy to read and understand.
Much more readable then for loop to my option.

Sometimes it's all about readability

Collapse
 
codingjlu profile image
codingjlu

How is

for(const val of arr) console.log(val)
Enter fullscreen mode Exit fullscreen mode

less readable than

arr.forEach(console.log)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lnahrf profile image
Lev Nahar • Edited

A simple for loop, or for of loop are extremely easy to read without all the downsides of forEach. I see absolutely no reason to use it.

Collapse
 
oyal profile image
oyal

Use splice to delete the remaining items and jump out of the loop directly.

Collapse
 
ravavyr profile image
Ravavyr

The REAL question here is:
What is the point of this interview question?
Seriously...this does not apply in any real world scenario since if you are looping like that 99% of the time you're looping through some interface elements that are limited in number and you don't care if it loops through all of them even if you don't need it to.

And no it won't impact performance unless you're writing bad code that loops through hundreds of elements needlessly, that's a different can of soup.

Oh no!, Are you the 1% doing something else?! omg omg! ...Use a for loop. The end.

No one needed to know the answer to this question. It adds zero value to an interview because knowing this vague bit of trivia does not in any way tell an interviewer if you know how to code.

Collapse
 
chrisburkssn profile image
Chris Burks

I think you have a point here about what is the point of the interview question. I think we get caught up in trying to prove that we know how to code that we forget that there are other aspects of "getting hired".
As I read through these comments I can see the difference in how well some can work with others and how some might brute force their opinions and how some are creative in how they solve for solutions.
With that said, I do think it's a great question to know the answer to as some may still be learning and not know when to use or not use a particular technique.

Collapse
 
lengyeltom profile image
lengyeltom

"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."
developer.mozilla.org/en-US/docs/W...

Collapse
 
kthurman59 profile image
Errantghost

They aren't asking it cause there is an answer, they are asking to see if you try to solve it. Also before answering, there are some basic questions to ask like: what is the forEach doing, why is it relentless, and why must it be a forEach if it is 'relentless' that implies the use case is, perhaps, an edge case of some sort. The point isn't to be clever and House MD an answer but to explore and to bring the interviewer along for the ride. Also I think the you could have a stopping mechanism in the function iterated over the elements with the forEach loop, maybe a return of some sort in the function body? Idk just spit balling.

Collapse
 
jankapunkt profile image
Jan Küster

This is why Array.prototype.some exists, if you need to stop you can return truthy value and it's ended.

Collapse
 
noriller profile image
Bruno Noriller

If someone asked me that, I would say no.
If they said "actually, you can just throw inside to stop it"
Then I would say there is a better way to do this instead of using forEach.

Collapse
 
bpathania profile image
Birender Pathania

`array.forEach(function(element) {
// Your code here
console.log(element);

if (condition {
break;
}
});`

Use it like this :)

Collapse
 
voltra profile image
Voltra

BTW, throwing an exception is how python implements its for loops (you can manually raise a StopIteration to break out of the loop)

Collapse
 
jimmywarting profile image
Jimmy Wärting

debugger;

Collapse
 
dilantha111 profile image
Dilantha

Love your writing style. "Cracking a nut with a sledgehammer" I literally pictured this in my mind :D ( And all the other references)

Collapse
 
pavithra_sandamini profile image
Pavithra Sandamini

Thank you for sharing

Collapse
 
oreoyona profile image
oreoyona

I do not JS I fear 😨

Collapse
 
voltra profile image
Voltra

Do not use exceptions as control flow

Collapse
 
yogini16 profile image
yogini16

Thanks for sharing !!

Collapse
 
mendosis profile image
Álvaro Mendoza

forEach is for each. you can use find if you want to stop after finding element matching criteria

Collapse
 
moorthidharmaraj profile image
moorthidharmaraj

Can't stop using return

Collapse
 
rosiewilliams39 profile image
clippingimages

Although I disagree with the answer, it is a legitimate one.