For-Loops vs forEach in Kotlin

Mike Gouline
3 min readMar 4, 2017

--

Because Kotlin has an abundance of features and it isn’t too opinionated, oftentimes you find yourself wondering which is a better way to achieve the same result. Is there any difference in readability, performance or extendability? This happens with even the most basic things like for-loops.

Integer Ranges

Let’s say you want to loop over a range of integers, you have two options:

  1. Traditional for-loop: for (i in 0..10) { ... }
  2. Functional approach: (0..10).forEach { i -> ... }

Both produce the same output, but do they work the same? This can be determined by compiling the Kotlin code and then decompiling it to Java (IntelliJ IDEA can help with that).

Here’s what the traditional for-loop looks like:

int i = 0;
byte var2 = 10;
if (i <= var2) {
while (true) {

if (i == var2) {
break;
}
++i;
}
}

And now the function approach:

Iterable $receiver$iv = (Iterable) (new IntRange(0, 10));
Iterator var2 = $receiver$iv.iterator();
while (var2.hasNext()) {
int element$iv = ((IntIterator) var2).nextInt();
...
}

Notice how forEach creates two additional objects, the integer range itself and its iterator, while the for-loop compiles down to a simple while-loop with primitive values. Not a huge difference, but that’s two object allocations you can avoid.

It’s also noteworthy how the forEach function was inlined into the while-loop, like in the for-loop, instead of creating a separate Function object. So no difference there.

Now let’s try changing the range to 10 downTo 0 step 2 and see how this affects the for-loop approach:

IntProgression var10000 = RangesKt.step(RangesKt.downTo(0, 10), 2);
int i = var10000.getFirst();
int var2 = var10000.getLast();
int var3 = var10000.getStep();
if (var3 > 0) {
if (i > var2) {
return;
}
} else if (i < var2) {
return;
}
while (true) {
...
if (i == var2) {
return;
}
i += var3;
}

The code is now more complex and allocates an object for the range but no iterator. Meanwhile, the functional approach hasn’t changed at all (hence it’s omitted). Traditional for-loop still winning.

Arrays and Lists

Fine, we understand ranges now. What about iterating over arrays?

Given nums: Array<Int>, we have two choices:

  1. for (i in nums) { ... }
  2. nums.forEach { i -> ... }

Here’s what #1 looks like decompiled:

Intrinsics.checkParameterIsNotNull(nums, "nums");

for (int var3 = 0; var3 < nums.length; ++var3) {
int i = nums[var3].intValue();
...
}

Compared to #2:

Intrinsics.checkParameterIsNotNull(nums, "nums");
Object[] $receiver$iv = (Object[]) nums;

for (int var3 = 0; var3 < $receiver$iv.length; ++var3) {
Object element$iv = $receiver$iv[var3];
int i = ((Number) element$iv).intValue();
...
}

We found a case where the two approaches converge: aside from the redundant casting between integers and objects, the functional approach looks identical to the for-loop.

You may be wondering, would lists be the same? Yes, except both approaches use the iterator solution:

Intrinsics.checkParameterIsNotNull(nums, "nums");
Iterator var3 = nums.iterator();

while (var3.hasNext()) {
int i = ((Number) var3.next()).intValue();
...
}

And with more redundant casting:

Intrinsics.checkParameterIsNotNull(nums, "nums");
Iterable $receiver$iv = (Iterable) nums;
Iterator var3 = $receiver$iv.iterator();

while (var3.hasNext()) {
Object element$iv = var3.next();
int i = ((Number) element$iv).intValue();
...
}

The same would apply to all other implementations of Iterable, including built-in types and anything custom you may conjure.

Conclusion

So there you have it, folks:

  • Traditional for-loops are slightly more memory efficient on numeric ranges and probably more readable for Java developers, albeit less “functional”
  • Functional forEach allocates one or two more objects on numeric ranges (depending on the conditions) but somewhat more consistent, especially when used around other functional operations (e.g. map, filter, etc.)
  • Both are identical when used on arrays, lists and other iterables
  • Practical differences in efficiency are minimal but may be enough to sway you one way or another if you were looking for any difference at all

Hopefully, this rumination on for-loops was illuminating — it’s as exciting as I could make it. If you find any problems with my methodology and/or results, please let me know and I will update the article.

--

--