DEV Community

loading...
Cover image for Why you should use Array.some instead of 'for' loop or forEach?

Why you should use Array.some instead of 'for' loop or forEach?

Yuvaraj
Technology Enthusiast
Originally published at yuvaraj.hashnode.dev ・3 min read

In this article, we are going to learn why we should use Array.some instead of Array.forEach (or) for loop.

Objective

In a given array, find if the student failed in any one of the subjects. The pass criteria for students is to score at least 40 marks in all the subjects.

const marks = [
  { name: "English", mark: 80 },
  { name: "Maths", mark: 100 },
  { name: "Science", mark: 38 },
  { name: "Social", mark: 89 }
];
Enter fullscreen mode Exit fullscreen mode

Traditional approach

Solution 1: Using Array.forEach

let isFailed = false;
marks.forEach((subject) => {
  console.log("checking subject => " + subject.name);
  if (subject.mark < 40) {
    // failed
    isFailed = true;
  }
});
console.log("Is student failed => " + isFailed);
Enter fullscreen mode Exit fullscreen mode

Output:

checking subject => English
checking subject => Maths
checking subject => Science
checking subject => Social

Is student failed => true

Enter fullscreen mode Exit fullscreen mode

The student is failed because he doesn't meet the pass criteria in the Science subject.

But, if you look at the output, it is unnecessary to check the Social subject because he failed in Science subject and the position of Science subject is before Social. So, in order to stop further checking, we can update the existing code as below:

let isFailed = false;
marks.forEach((subject) => {
 // added this condition to prevent further checking
  if (!isFailed) {
    console.log("checking subject => " + subject.name);
    if (subject.mark < 40) {
      // failed
      isFailed = true;
    }
  }
});
console.log("Is student failed => " + isFailed);
Enter fullscreen mode Exit fullscreen mode

Output:

checking subject => English
checking subject => Maths
checking subject => Science

Is student failed => true

Enter fullscreen mode Exit fullscreen mode

This looks like we have solved the issue but it's not. Even though we wrapped our logic inside if
block, the iteration still happens. Try checking it by adding else block.

Imagine if we have 1000 elements in an Array and if the condition to fail is at 10th position, then the remaining 990 iteration still runs which is not needed. It takes both time & computation. 🤯

So, this is the Wrong solution to this problem. ❌

Let's move on to the second traditional approach.

Solution 2: Using for() loop

let isFailed = false;
for (i = 0; i <= marks.length; i++) {
  const subject = marks[i];
  console.log("checking subject => " + subject.name);
  if (subject.mark < 40) {
    // failed
    isFailed = true;
    // prevents further execution
    break;
  }
}

console.log("Is student failed => " + isFailed);
Enter fullscreen mode Exit fullscreen mode

This solution is better than the previous approach. The reason is, when the fail condition is met, further iteration is stopped with break keyword.

The break statement is used to jump out of a loop

The problem with this approach is, this is not the right way to solve this problem. Like how we use the for loop & Array.forEach to iterate the Array, there is an inbuilt Array method to solve this problem.

So, this is also not a correct solution. ❌

Let's see in the next section!


Correct Approach

The correct approach to solve this problem is to use, Array.prototype.some().

From MDN ,

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn't modify the array.

This is what we wanted. If at least one element passes the condition, it should return true otherwise it should return as false.

Here is the solution to our problem,

const isFailed = marks.some((subject) => subject.mark < 40);
console.log("Is student failed => " + isFailed); // true
Enter fullscreen mode Exit fullscreen mode

This works as expected. The solution is in a single line. 🤯

But, how do we know, if further execution is stopped once the condition is met?

Let's check it out by updating the code.

const isFailed = marks.some((subject) => {
  console.log("checking subject => " + subject.name);
  return subject.mark < 40;
});
console.log("Is student failed => " + isFailed);
Enter fullscreen mode Exit fullscreen mode

Output:

checking subject => English
checking subject => Maths
checking subject => Science

Is student failed => true

Enter fullscreen mode Exit fullscreen mode

The code works as expected.

So, this is the correct solution to solve our problem. ✅

Now, the Code is much readable, simpler & efficient than the other approaches.

I hope you enjoyed this article or found it helpful.

You can connect with me on Twitter & Github 🙂

Support 🙌

Buy me a coffee

Discussion (18)

Collapse
psiho profile image
Mirko Vukušić • Edited

Hm, it is a nice introduction to array.some() but I don't think it's completely explained and some might pull wrong conclusions from this. Using "for" cannot be considered "wrong", especially not with explanation of why it would be wrong: "The problem with this approach is, this is not the right way to solve this problem.". So, it is wrong because it is wrong?

To be specific about array.some(), yes, it's hard to beat it in speed and simplicity. But it's also not hard to match it (in speed). What is left in that case is readability and simplicity of the code which makes it a clear winner in your example.
However, some might think that all of those "new" methods are always better and that "for" is incorrect, and that is not true. More often than not, new methods win in simplicity and readability but sacrifice speed. For most cases, speed difference is so small that it is unimportant, but when dealing with large arrays (and/or process them often), faster solution beats readable solution.

And again, it's not difficult to match "new" methods with "old" for-loops when it comes to speed. In fact, I don't know of a single case when you cannot match it with for-loops. New methods are never faster, they're just equally fast at best, and quite often... slower. So, for some performance oriented system, it's actually easier to always use for-loops and stop thinking if some of new methods is slower or equally fast... you just can never loose with for-loop in this case.

What I'm trying to say is that performance vs readability is not mentioned at all here, and naming "for" solution simply "wrong" is incorrect in my opinion. But yes, if you just don't need/want to care about performance then article is completely correct.

Example (benchmark) how to match performance of some() (and how NOT to try and match it) is here: jsbench.me/3ykr5dwv5l/1
It also serves as a good advocate to some(), because you can see it's highly optimized. i++ cannot beat it! You have to know about i-- trick to save that extra 7% (at least in Chrome)

Collapse
lukeshiru profile image
LUKESHIRU

Generally there are performance gains from doing it with for, but from my point of view, readability is way more valuable.

If you run into a scenario in which the performance difference matters (like those arrays of 50000 items that folks love to mention when talking about performance), then the problem is not with the array method, but actually with data itself. Those volumes of data should be handled using streaming methods, or just splitting them into smaller chunks.

Still, I agree with you that the author should have said that the main benefit is that is a way nicer syntax, at the cost of a little bit of performance (which generally doesn't matter that much).

Collapse
psiho profile image
Mirko Vukušić

Yes, agree. It also needs to be said that often people overoptimize code for speed wasting readibility. In vast majority of cases it's absolutely not needed, especially not in places like this. But I'm ol school, started with assembler and only like 32 KB of memory so I got into habbit... Not to optimize every case, but to learn and know how things work and try to get good habbits, so relatively optimized code is written right from start. I admit that my treshold for wasting resources is lower :)
In practice, what I can think of are games, where each frame counts. Also, I do a lot of business apps, like erp, and sometimes I need to agregate thousands of records (like invoices) client side (because server side is external api). Splitting data when agreggating is not an option. In those cases I always go to for-loops, even if couple thousands are still not a huge reason to optimize. In thise cases, I "solve" readibility by extracting loops to helper functions which are properly named.

Collapse
luk492 profile image
Luka Kajtes

I totally agree with your comment. Plus this is the polyfill for the "some" implementation. Good old for, just sayin...

if (this == null) {
  throw new TypeError('Array.prototype.some called on null or undefined');
}

if (typeof fun !== 'function') {
  throw new TypeError();
}

var t = Object(this);
var len = t.length >>> 0;

for (var i = 0; i < len; i++) {
  if (i in t && fun.call(thisArg, t[i], i, t)) {
    return true;
  }
}

return false;
Enter fullscreen mode Exit fullscreen mode
Collapse
mazentouati profile image
Mazen Touati

I think some is confusing when introduced alone like in this article. It would be insightful if it's coupled with the contrasting function every.

I would like to add to what you've said that these all new methods are just new built-in functions in the Array's prototype. They are not language constructs. Hence, the looping constructs (for, while, etc.) have to be used anyway behind the scene.

Collapse
sannajammeh5 profile image
Sanna Jammeh

Behind the scenes V8 does not compile forEach, map, reduce etc to a for loop and then executes. It has its very own implementation within the runtime at the lowest possible level.

Only for polyfills where the engine does not have an implementation will you see a behind the scenes "for" or "while" loop.

Thread Thread
mazentouati profile image
Mazen Touati

Thank you for bringing this up. Indeed, V8 pre-compiles it to bytecode to enhance performance and to optimize memory usage. My comment made it sound like they're some kind of a library written in JS and being imported to your code to add new functions to the Array's protytpe. That's what I was thinking to be honest. After checking, it turns out V8 uses its own language V8 Torque and pre-compiles it to bytecode. Here's Array.some source code for example.

However, what I've tried to say is that it has to loop through the items at some point (even in the lowest level). Also, These optimations are not exclusive to the built-in functions. V8 will optimize your code too. For instance, most of the benchmarks I've consulted state that native for loops are faster than forEach, reduce, map etc.

The whole point was to point out that using For loops is not a wrong approach unlike what's being said in this Article.

Collapse
lukeshiru profile image
LUKESHIRU

Nice one! One thing worth mentioning is that you can also use Array.prototype.every, which works in the exact oposite way. While Array.prototype.some runs while the predicate returns false and stops when it returns true, Array.prototype.every runs while the predicate returns true and stops when it returns false:

const isApproved = marks.every(({ mark }) => mark >= 40);
console.log(`Is student approved => ${isApproved}`); // false
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
bentrancodes profile image
benTranCodes

Nice article! I’m all for using Array.some since it helps makes things much easier to read/write. I believe that if performance matters in the scenario, then it might be worth while to use the for…loop solution since it should be more optimized.

Collapse
jwhenry3 profile image
Justin Henry

We should also be talking about, not only performance, but blocking. Regardless the function or mechanism you use to iterate, you should give thought to how many records you are cycling through and how much that impacts the rendering of the DOM.

If you find yourself iterating over thousands of records to perform an operation, you can use something like rxjs or some promise based iteration to work around that.

We should stop caring so much on which function you use for iteration, and care more about the implications for the application as a whole. Performance is great up until that performance still heavily blocks the application.

Collapse
sharpninja profile image
The Sharp Ninja

This just points out why JavaScript, especially in a UI, is almost always the wrong platform for processing anything. If you are calling an inflexible 3rd party that gives you back huge chunks of data, place an API on you own server to process that data before it goes to the UI. And that API should not be written in JavaScript because the goal is parallelizing the processing.

Collapse
jwhenry3 profile image
Justin Henry

thankfully there are libs out there that are fully aware of the limitations of javascript and help with processing on separate "threads" or workers.

Collapse
darkwiiplayer profile image
DarkWiiPlayer

some is a very weird name; any (as used in other programming languages) seems like a much better name to me.

Collapse
sharpninja profile image
The Sharp Ninja

You know, gotta be different from C# because cannot acknowledge LINQ has existed for 13 years.

Collapse
sharpninja profile image
The Sharp Ninja

This bull crap. It is not more efficien than the for loop because of the overhead of calling the arrow function on each item until a match is found. It is more cosmetically pleasing, but definitely not more efficient.

Collapse
rakeshyadavs9 profile image
Rakesh Yadav

Skipped entire article when you said "This is not correct solution" for for loop.

Collapse
drfcozapata profile image
Francisco Zapata

Great!!!
I loved it!

Collapse
ayabouchiha profile image
Aya Bouchiha

Nice!