Filip Hráček / text /
Quiz question: which is a faster way to iterate over a large list of numbers?
A
// The classic for loop.
for (var i = 0; i < list.length; i++) {
sum += pow(list[i], 3);
}
B
// The more functional-style Iterable.forEach.
list.forEach((element) {
sum += pow(element, 3);
});
Long years of folk programmer wisdom, and at least one microbenchmark (from 2021) would say that A is significantly faster than B. Because, in A you are running a classic loop, while in B you are calling a closure for every iteration. Right? So the answer is obviously A, right?
The real answer, of course, is C: it depends. And I have data to back that up.
Like in September with Flutter state management libraries, this month I decided to use my performance-lab-in-the-making to measure something slightly silly. This time, it’s loops.
All the caveats about microbenchmarks still apply. They are less generally applicable than most people think. Even if they’re applicable, they often measure efficiency of something that’s so miniscule in magnitude, it doesn’t really matter. You’re optimizing a fraction of a fraction of your program’s runtime. (See: Amdahl’s law.)
Most Dart programs (and Flutter apps) don’t iterate over a huge list of num. In fact, very few Dart programs iterate over huge (105+) lists of anything. If you have an app like
return ListView(
children: [
// ... 100,000 children ...
],
);
then you have worse problems than how quickly Dart can iterate over your list. If you’re acting on a list this big in Dart, than the iteration itself is probably the least of your worries.
Also, iterating over a list of numbers can result in a very different machine code than iterating over a list of some more complex type.
In other words: measure what your program is actually doing, don’t just carry a bunch of microbenchmarks in your head and pattern match them with your code.
But here, we allow ourselves to be silly. Let’s imagine we really need to sum a bunch of numbers, and we need to do it as quickly as possible. Do we use while, for, for-in, forEach? Do we cache the length of the list? Does direction matter?
If you run a benchmark on your workstation, with Just-in-Time (JIT) compilation, you will confirm your suspicion that the for loop is faster than the forEach.
In fact, version A is more than 20% faster! And this is a statistically significant result, with N=400 independent, intertwined runs (i.e., it’s not just 400 iterations, nor is it running benchmark_harness a few times and calling it a day).
But then, you add Ahead-of-Time (AOT) compilation into the mix:
They’re... the same?
Yep! In AOT, Dart is smart enough to see what’s going on with forEach(), and (presumably, I haven’t checked) outputs a very similar piece of machine code as with the for loop.
Of course, we don’t really care how fast things are on a devbox — we care how fast it is on users' mobile devices. Thankfully, in this case at least, the results are similar to what I see on my Mac when using AOT mode.
There you go, choosing a for loop over a forEach() invocation is no longer a performance thing. It seems that, in this simple case at least, the two approaches are pretty much equivalent. Take a pick based on code style, readability, and so on.
But wait. What about while loops? Those seem like an even more basic type of loop, and if you've ever written any assembly code, you know that, in structure, while loops are the closest to how we write cycles at the lowest level.
var count = 0;
while (count < list.length) {
sum += pow(list[count], 3);
count++;
}
So maybe they’re faster in Dart?
Nope. There is no significant difference.
At this point you might be thinking that this article is just a long way to say that there are no performance differences between any of the loops. But that’s not so. Let’s consider the much more modern for-in loop, where you don’t even have to juggle with the index:
for (final element in list) {
sum += pow(element, 3);
}
You might think that because the language does a bit of extra work for you, it’s slower. But no, the for-in loop is (in this specific case, at least!) actually faster than the baseline for loop:
(I should clarify that this is not the case across the board. The result above is from a real Android device, but on my laptop in AOT mode, the for loop is slightly faster than the for-in loop.)
So clearly we should just use for-in loops everywhere? Where it makes sense, and you’re simply iterating over something, sure. But if you need to iterate in reverse, for example, classic for-loop easily wins over for (final element in list.reversed):
And when you need to iterate and at the same time have access to the index?
for (final (index, element) in list.indexed) {
doNotOptimizeAway(index);
sum += pow(element, 3);
}
Unfortunately, I didn't think of benchmarking list.indexed on the device, but on my laptop at least, using AOT (which, unsurprisingly, tends to be closest in results to the AOT running on mobile devices), iterating over list.indexed is not slower than a simple for loop.
Stepping back from for-in, maybe the reason the for loops and the while loops aren’t as fast as they could be is because we are accessing list.length on every iteration? And maybe Dart isn’t smart enough to avoid this field access, or even worse, computes the length every time? So maybe let’s cache the length before the loop?
final length = list.length;
for (var i = 0; i < length; i++) {
sum += pow(list[i], 3);
}
Nope, there is no significant difference:
In other words, Dart is smart enough to realize it doesn’t need to access that length for every iteration. Otherwise, I’m pretty sure we’d see real improvement from the length caching above.
So that’s about how much I was able to learn from my benchmark on a real Android device. Here’s the complete comparison of runs, including ones I didn't think interesting enought to call out in this article:
So that’s how things are on a device. How about the web? package:benchmark_harness allows you to test Dart running in AOT and JIT mode, but also compiled to JavaScript and to WASM (Web Assembly), running on node.js. I didn't measure JavaScript because I think most people who care about performance will use WASM rather than JavaScript. For WASM, the results are here:
For what it’s worth, the results are remarkably close when compiled to WASM, whichever loop approach you pick. Once again, forEach() is not any slower than the other loops.
While you’re here, I might as well show how different flavors (AOT, JIT, WASM) compare to each other (on a laptop). Going again with a simple for loop as a point of comparison, here’s the result:
Interesting, right? AOT mode is relatively slow when running a loop like this on a laptop. This shouldn’t really be that surprising, since Just-in-Time optimization has a lot more information to work with than Ahead-of-Time (optimizing) compilation, but people tend to forget this. If you can run in JIT mode, it’s often worth trying at least. That said, on mobile devices, AOT seems to almost always be the better choice, and so that’s what’s used there.
Aside: Please don’t take away from this that you should unconditionally move everything to JIT. Remember all the caveats from earlier in this article.
What’s interesting is that WASM performs so much better than AOT. I wouldn’t be surprised, though, if this was just the microbenchmark effect. Before saying anything definitive about AOT vs JIT vs WASM, I’d want to benchmark something more real than a simple loop over a simple list of numbers.
What I do want you to take away from this article is that a lot of the “obvious”, widely-held beliefs about Dart performance are simply wrong. Don’t go around replacing forEach()s with while loops. Measure first. Don’t let an LLM make your codebase slow and more complex just because it feels that a particular piece of code will be faster. Don’t draw conclusions from microbenchmarks that are wrong, hallucinated, or running on the wrong device.
(Incidentally, if you or your organization have a performance issue that you’d like to address, I have some time in the coming months. Work with me.)
— Filip Hráček
October 2026