Blog Infos
Author
Published
Topics
, , , ,
Published

 

Kotlin makes loops elegant and expressive — but not all loops are equal, especially when you’re chasing milliseconds on LeetCode.

I recently noticed something interesting:

The same algorithm using a forEach loop was taking 12ms, but when I rewrote it using a for loop, it dropped to 2ms. Intrigued, I decided to dig into why this happens — and here’s what I found.

// Using for-loop
    for (i in 0 until 100_000) {
        println(i)
    }

// Using forEach
    (0 until 100_000).forEach {
        println(it)
    }

Both snippets look like they do the same thing, but the performance difference is real.

Let’s run this and see what’s the result. (Wrapped this call with measureTimeMillis to measure the time)

For: 101ms
ForEach: 111ms
Difference: 10ms (9% increase in time)

Wait, this seems off, the difference of time in leetcode does not add up.

“…after reading through some docs, I stumbled upon JIT (Java’s Just In Time Compiler). It optimizes methods that are called frequently by compiling them to native code at runtime. Since I repeatedly called println(), JIT likely optimized it heavily, masking the real cost of the loop itself.”

To avoid JIT interfering, I replaced println() with a small arithmetic calculation

//For
for (i in 0 until 100_000) {
     result += i
}

// ForEach 
(0 until 100_000).forEach {
    result += it
}

For : 1ms
ForEach: 6ms
Difference: 6ms (500% increase in time)

You can see a more granular difference if you use the measureNanoTime method to measure

For: 648084 ns
ForEach: 5411916 ns
Difference: 47,63,832 ns (800% increase in time)

But wait, what causes is the difference here?
// For (After decompiling to Java): This seems to be a simple forLoop

public static final void main() {
   int result = 0;
   for(int i = 0; i < 100000; ++i) {
      result += i;
   }
}

 

// ForEach Loop (After decompiling to Java):
public static final void main() {
      int result = 0;
      Iterable $this$forEach$iv = (Iterable)RangesKt.until(0, 100000);
      int $i$f$forEach = 0;
      int element$iv;
      for(Iterator var3 = $this$forEach$iv.iterator(); var3.hasNext(); result += element$iv) {
         element$iv = ((IntIterator)var3).nextInt();
         int var6 = 0;
      }
   }

 

The difference is very clear here. (0 until 100_000) creates a Iterable object and a Iterator is used to iterate through all the elements in the given range. This creates additional overhead on the system, as calling nextInt to just get a value is going to be costlier than just directly accessing it.

Btw this was not the same result when I used for and forEach on a simple list — both decompiled into a simple for loop and the results were almost same or forEach was faster in some cases.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

Conclusion:

Even though Kotlin marks forEach as inline to reduce overhead, the creation of an Iterator still happens in this case, and that’s where the hidden cost comes from.

The tricky part? This overhead isn’t always obvious — it’s hidden behind a clean syntax and functional style. But under the hood, the creation and use of an Iterator does cost system time, especially in tight, performance-sensitive loops.

The catch here is that you won’t always know what’s happening under the hood. Kotlin may even change how it decompiles things in future versions, so today’s performance observations might differ tomorrow.

  • Don’t assume that a for loop is always better than forEach. The right choice depends on the context — and can vary from case to case. Do your own research, test in your specific context, and consider factors like readability, maintainability, and performance before deciding which loop to use.
  • Always think about factors like JIT when quantifying some piece of code.

If this saved you a few milliseconds (or helped you win a LeetCode race), give it a few claps — or drop your favorite Kotlin micro-optimization in the comments. 🥂

This article was previously published on proandroiddev.com.

Menu