Every Groovy for loop variation is covered here with 14 tested examples. Classic for, for-in, ranges, each, and more. Complete guide for Groovy 5.x.
“Any fool can write a loop that works. A good developer writes a loop that’s readable, efficient, and idiomatic.”
Edsger Dijkstra, A Discipline of Programming
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 16 minutes
Loops are the backbone of programming. Processing a list of users, crunching numbers, building output strings – you’ll need a Groovy for loop at some point. And Groovy gives you more looping options than you might expect.
If you’re coming from Java, you already know the classic C-style for (int i = 0; i < n; i++) loop. Groovy supports that, but it also offers for-in loops with ranges, lists, maps, and strings. On top of that, Groovy’s GDK adds methods like each() and times() that make iteration feel effortless.
In this post, we’ll cover every Groovy for loop variation with 14 tested examples. Each example shows exactly which loop style to use and when. If you’re new to Groovy collections, start with our Groovy Collections Overview first.
Table of Contents
What Is a For Loop in Groovy?
A Groovy for loop is a control flow statement that repeatedly executes a block of code. Groovy supports two main forms: the classic C-style for loop and the enhanced for-in loop. The for-in loop is the idiomatic Groovy choice because it works with ranges, lists, maps, strings, and any iterable object.
According to the official Groovy semantics documentation, Groovy’s for-in loop is more versatile than Java’s enhanced for loop. It supports Groovy ranges, any Iterable, arrays, maps (iterating over entries), and even characters in a string.
Key Points:
- Classic C-style
for (init; condition; update)works just like Java for (item in collection)is the idiomatic Groovy form- Groovy ranges (
1..10,1..<10) make numeric loops clean and readable - Maps iterate as key-value entries automatically
- The GDK adds
each(),eachWithIndex(), andtimes()as alternatives breakandcontinuework with for loops but NOT witheach()
Why Use For Loops in Groovy?
You might be wondering – if Groovy has each() and collect(), why bother with for loops at all? Great question. Here’s why for loops still matter:
- Break and continue support: You can exit early with
breakor skip iterations withcontinue– somethingeach()cannot do - Readability: For simple iterations, a for loop is immediately clear to any developer
- Index control: C-style for loops give you precise control over start, end, and step values
- Performance: For loops have slightly less overhead than closure-based methods
- Java compatibility: Java developers joining your team will instantly understand for loops
That said, idiomatic Groovy code often prefers each() for simple iteration and times() for repeat-N-times scenarios. Use whichever makes your code clearest.
Groovy For Loop Syntax
Classic C-Style For Loop
C-Style Syntax
// Standard C-style for loop
for (int i = 0; i < n; i++) {
// code
}
// Groovy style (def instead of int)
for (def i = 0; i < n; i++) {
// code
}
For-In Loop (Idiomatic Groovy)
For-In Syntax
// With a range
for (i in 1..10) { }
// With a list
for (item in [1, 2, 3]) { }
// With a map
for (entry in map) { }
// With a string (iterates characters)
for (ch in "hello") { }
Comparison Table
| Syntax | Use Case | break/continue | Index Access |
|---|---|---|---|
for (i = 0; i < n; i++) | Precise index control | Yes | Built-in |
for (item in collection) | Iterate any iterable | Yes | Manual |
for (i in 0..n) | Range-based counting | Yes | Built-in |
collection.each { } | Functional style | No | eachWithIndex |
n.times { } | Repeat N times | No | Closure param |
14 Practical Groovy For Loop Examples
Example 1: Classic C-Style For Loop
Let’s start with the familiar C-style for loop. If you’re coming from Java or C, this will feel right at home. It gives you full control over the counter variable, the termination condition, and the step.
Example 1: Classic C-Style For Loop
// Classic C-style for loop
println "Counting from 1 to 5:"
for (int i = 1; i <= 5; i++) {
print "${i} "
}
println()
// Counting down
println "\nCountdown:"
for (int i = 5; i >= 1; i--) {
print "${i} "
}
println()
// Step by 2
println "\nEven numbers up to 10:"
for (int i = 2; i <= 10; i += 2) {
print "${i} "
}
println()
Output
Counting from 1 to 5: 1 2 3 4 5 Countdown: 5 4 3 2 1 Even numbers up to 10: 2 4 6 8 10
Nothing surprising here – it works exactly like Java. But honestly, in Groovy you’ll rarely write C-style loops. The for-in loop with ranges is much cleaner for these use cases.
Example 2: For-In with Lists
The for-in loop is Groovy’s enhanced for loop. When you use it with a list, you iterate directly over the elements – no index variable needed.
Example 2: For-In with Lists
// Iterate over a list of strings
def languages = ['Groovy', 'Java', 'Kotlin', 'Scala']
println "Programming Languages:"
for (lang in languages) {
println " - ${lang}"
}
// Iterate over a list of numbers
def scores = [95, 82, 78, 91, 88]
def total = 0
for (score in scores) {
total += score
}
println "\nScores: ${scores}"
println "Total: ${total}"
println "Average: ${total / scores.size()}"
Output
Programming Languages: - Groovy - Java - Kotlin - Scala Scores: [95, 82, 78, 91, 88] Total: 434 Average: 86.8
Notice how clean this is compared to the C-style approach. No i variable, no size() call, no get(i). You just get each element directly.
Example 3: For-In with Ranges
Ranges are one of Groovy’s best features. A range like 1..5 creates a sequence from 1 to 5 (inclusive), and 1..<5 creates a sequence from 1 to 4 (exclusive upper bound).
Example 3: For-In with Ranges
// Inclusive range (1 to 5)
println "Inclusive range 1..5:"
for (i in 1..5) {
print "${i} "
}
println()
// Exclusive range (1 to 4, excluding 5)
println "\nExclusive range 1..<5:"
for (i in 1..<5) {
print "${i} "
}
println()
// Reverse range
println "\nReverse range 5..1:"
for (i in 5..1) {
print "${i} "
}
println()
// Character range
println "\nCharacter range 'a'..'f':"
for (ch in 'a'..'f') {
print "${ch} "
}
println()
// Range with step (using step method)
println "\nRange with step of 3:"
for (i in (1..15).step(3)) {
print "${i} "
}
println()
Output
Inclusive range 1..5: 1 2 3 4 5 Exclusive range 1..<5: 1 2 3 4 Reverse range 5..1: 5 4 3 2 1 Character range 'a'..'f': a b c d e f Range with step of 3: 1 4 7 10 13
Ranges are incredibly useful. They replace the C-style for (int i = 1; i <= 5; i++) with the much cleaner for (i in 1..5). And you can even iterate over characters – try doing that elegantly in Java!
Example 4: For-In with Maps
When you loop over a map in Groovy, each iteration gives you a Map.Entry with .key and .value properties. It’s one of those things that just feels right.
Example 4: For-In with Maps
// Iterate over map entries
def capitals = [
India : 'New Delhi',
Japan : 'Tokyo',
France : 'Paris',
Brazil : 'Brasilia'
]
println "World Capitals:"
for (entry in capitals) {
println " ${entry.key} → ${entry.value}"
}
// Destructuring map entries
println "\nWith destructuring:"
for (entry in capitals) {
def (country, capital) = [entry.key, entry.value]
println " The capital of ${country} is ${capital}"
}
// Iterate over just keys or values
println "\nJust the countries:"
for (country in capitals.keySet()) {
print "${country} "
}
println()
println "\nJust the capitals:"
for (capital in capitals.values()) {
print "${capital} "
}
println()
Output
World Capitals: India → New Delhi Japan → Tokyo France → Paris Brazil → Brasilia With destructuring: The capital of India is New Delhi The capital of Japan is Tokyo The capital of France is Paris The capital of Brazil is Brasilia Just the countries: India Japan France Brazil Just the capitals: New Delhi Tokyo Paris Brasilia
Map iteration in Groovy is smooth. Each entry is a Map.Entry object, so you access .key and .value directly. You can also loop over just keys or just values using keySet() and values().
Example 5: For-In with Strings
Here’s something cool – you can iterate directly over a string’s characters using for-in. No need to call toCharArray() or charAt().
Example 5: For-In with Strings
// Iterate over characters in a string
def word = "Groovy"
println "Characters in '${word}':"
for (ch in word) {
print "[${ch}] "
}
println()
// Count vowels
def text = "Groovy for loops are awesome"
def vowels = 0
for (ch in text.toLowerCase()) {
if (ch in ['a', 'e', 'i', 'o', 'u']) {
vowels++
}
}
println "\nText: '${text}'"
println "Vowel count: ${vowels}"
// Build a character frequency map
def freq = [:]
for (ch in "mississippi") {
freq[ch] = (freq[ch] ?: 0) + 1
}
println "\nCharacter frequency of 'mississippi':"
for (entry in freq) {
println " '${entry.key}' → ${entry.value}"
}
Output
Characters in 'Groovy': [G] [r] [o] [o] [v] [y] Text: 'Groovy for loops are awesome' Vowel count: 11 Character frequency of 'mississippi': 'm' → 1 'i' → 4 's' → 4 'p' → 2
String iteration is perfect for character-level processing. The vowel counter and frequency map examples show how you can combine for-in with other Groovy features for concise solutions.
Example 6: For Loop with Step
Sometimes you need to count by 2s, 3s, or some other interval. The C-style loop handles this with i += step, but Groovy ranges with .step() are cleaner.
Example 6: For Loop with Step
// Step with C-style for loop
println "Odd numbers 1-20 (C-style):"
for (int i = 1; i <= 20; i += 2) {
print "${i} "
}
println()
// Step with range
println "\nMultiples of 5 up to 50:"
for (i in (5..50).step(5)) {
print "${i} "
}
println()
// Reverse step
println "\nCountdown by 3 from 30:"
for (i in (30..1).step(3)) {
print "${i} "
}
println()
// Practical: print multiplication table row
def n = 7
println "\n\nMultiplication table for ${n}:"
for (i in 1..10) {
println " ${n} x ${i} = ${n * i}"
}
Output
Odd numbers 1-20 (C-style): 1 3 5 7 9 11 13 15 17 19 Multiples of 5 up to 50: 5 10 15 20 25 30 35 40 45 50 Countdown by 3 from 30: 30 27 24 21 18 15 12 9 6 3 Multiplication table for 7: 7 x 1 = 7 7 x 2 = 14 7 x 3 = 21 7 x 4 = 28 7 x 5 = 35 7 x 6 = 42 7 x 7 = 49 7 x 8 = 56 7 x 9 = 63 7 x 10 = 70
The .step() method on ranges is a nice Groovy-specific feature. It’s cleaner than i += 2 because your intent is immediately obvious.
Example 7: Enhanced For Loop with Objects
Groovy’s for-in works with any Iterable, including custom objects, sets, queues, and arrays. Here it is in action with different types.
Example 7: Enhanced For Loop with Objects
// With an array
int[] numbers = [10, 20, 30, 40, 50]
println "Array elements:"
for (num in numbers) {
print "${num} "
}
println()
// With a Set (unordered, no duplicates)
def uniqueColors = ['red', 'blue', 'green', 'red', 'blue'] as Set
println "\nUnique colors:"
for (color in uniqueColors) {
print "${color} "
}
println()
// With a custom class implementing Iterator
class Countdown implements Iterable<Integer> {
int start
Iterator<Integer> iterator() {
def current = start
return [
hasNext: { current > 0 },
next: { current-- }
] as Iterator<Integer>
}
}
println "\nCustom Countdown from 5:"
for (n in new Countdown(start: 5)) {
print "${n} "
}
println()
// With an Enum
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
println "\nSeasons:"
for (s in Season.values()) {
println " ${s.name().toLowerCase().capitalize()}"
}
Output
Array elements: 10 20 30 40 50 Unique colors: red blue green Custom Countdown from 5: 5 4 3 2 1 Seasons: Spring Summer Autumn Winter
The beauty of for-in is its universality. Arrays, sets, custom iterables, enums – same syntax, different types. Groovy’s duck typing makes this possible.
Example 8: Nested For Loops
Nested loops are common when working with 2D data structures, matrices, or when you need to compare every pair of items. Here’s how they work in Groovy.
Example 8: Nested For Loops
// Print a simple pattern
println "Triangle pattern:"
for (i in 1..5) {
def row = ""
for (j in 1..i) {
row += "* "
}
println row.trim()
}
// 2D list (matrix) traversal
def matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
println "\nMatrix:"
for (row in matrix) {
def line = ""
for (cell in row) {
line += String.format("%3d", cell)
}
println line
}
// Find pairs that sum to a target
def nums = [2, 7, 11, 15, 4]
def target = 9
println "\nPairs that sum to ${target}:"
for (i in 0..<nums.size()) {
for (j in (i + 1)..<nums.size()) {
if (nums[i] + nums[j] == target) {
println " ${nums[i]} + ${nums[j]} = ${target}"
}
}
}
Output
Triangle pattern: * * * * * * * * * * * * * * * Matrix: 1 2 3 4 5 6 7 8 9 Pairs that sum to 9: 2 + 7 = 9
Nested loops are simple. The pair-finding example uses exclusive ranges (0..<nums.size()) to avoid index out-of-bounds errors – the ..< operator excludes the upper bound.
Example 9: Labeled Break and Continue
Groovy supports labeled loops, just like Java. Labels let you break out of or continue an outer loop from inside an inner loop. This is one of the key advantages of for loops over each().
Example 9: Labeled Break and Continue
// Simple break - find first negative number
def values = [5, 12, -3, 8, -7, 15]
println "Finding first negative number:"
for (v in values) {
if (v < 0) {
println " Found: ${v}"
break
}
println " Checked: ${v} (positive)"
}
// Simple continue - skip even numbers
println "\nOdd numbers only:"
for (i in 1..10) {
if (i % 2 == 0) continue
print "${i} "
}
println()
// Labeled break - exit outer loop from inner loop
println "\nLabeled break (find first duplicate):"
def items = ['apple', 'banana', 'cherry', 'banana', 'date']
outer:
for (i in 0..<items.size()) {
for (j in (i + 1)..<items.size()) {
if (items[i] == items[j]) {
println " Duplicate found: '${items[i]}' at index ${i} and ${j}"
break outer
}
}
}
// Labeled continue - skip specific outer iterations
println "\nLabeled continue (skip rows with zeros):"
def data = [
[1, 2, 3],
[4, 0, 6],
[7, 8, 9]
]
outer:
for (row in data) {
for (cell in row) {
if (cell == 0) {
println " Skipping row with zero: ${row}"
continue outer
}
}
println " Processing row: ${row} → sum = ${row.sum()}"
}
Output
Finding first negative number: Checked: 5 (positive) Checked: 12 (positive) Found: -3 Odd numbers only: 1 3 5 7 9 Labeled break (find first duplicate): Duplicate found: 'banana' at index 1 and 3 Labeled continue (skip rows with zeros): Processing row: [1, 2, 3] → sum = 6 Skipping row with zero: [4, 0, 6] Processing row: [7, 8, 9] → sum = 24
This is where for loops really shine over each(). You cannot use break or continue inside an each() closure – they only work inside traditional loops. If you need to exit early, use a for loop.
Example 10: For vs Each vs Times Comparison
Let’s put all three looping approaches side by side so you can see the differences clearly. Each approach has its strengths, and knowing when to pick which one is key to writing idiomatic Groovy.
Example 10: For vs Each vs Times
def fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry']
// Approach 1: Classic for-in loop
println "=== for-in loop ==="
for (fruit in fruits) {
println " ${fruit.toUpperCase()}"
}
// Approach 2: each() closure
println "\n=== each() closure ==="
fruits.each { fruit ->
println " ${fruit.toUpperCase()}"
}
// Approach 3: eachWithIndex (when you need the index)
println "\n=== eachWithIndex() ==="
fruits.eachWithIndex { fruit, idx ->
println " ${idx + 1}. ${fruit}"
}
// Approach 4: times() for simple counting
println "\n=== 3.times() ==="
3.times { i ->
println " Iteration ${i}"
}
// Approach 5: for-in with index (manual)
println "\n=== for-in with manual index ==="
def idx = 0
for (fruit in fruits) {
println " [${idx}] ${fruit}"
idx++
}
// Key difference: break works in for, not in each
println "\n=== break in for loop ==="
for (fruit in fruits) {
if (fruit.startsWith('c')) {
println " Stopped at: ${fruit}"
break
}
println " Processing: ${fruit}"
}
Output
=== for-in loop === APPLE BANANA CHERRY DATE ELDERBERRY === each() closure === APPLE BANANA CHERRY DATE ELDERBERRY === eachWithIndex() === 1. apple 2. banana 3. cherry 4. date 5. elderberry === 3.times() === Iteration 0 Iteration 1 Iteration 2 === for-in with manual index === [0] apple [1] banana [2] cherry [3] date [4] elderberry === break in for loop === Processing: apple Processing: banana Stopped at: cherry
Here’s a quick rule of thumb: use each() for simple iteration, times() for repeat-N scenarios, and for-in when you need break/continue or more complex control flow.
Example 11: Looping with Index
Sometimes you need both the element and its position. Groovy gives you multiple ways to loop with an index.
Example 11: Looping with Index
def colors = ['red', 'green', 'blue', 'yellow', 'purple']
// Method 1: C-style for loop
println "Method 1: C-style index"
for (int i = 0; i < colors.size(); i++) {
println " colors[${i}] = ${colors[i]}"
}
// Method 2: for-in with range as index
println "\nMethod 2: Range-based index"
for (i in 0..<colors.size()) {
println " colors[${i}] = ${colors[i]}"
}
// Method 3: indexed() method (Groovy 2.4+)
println "\nMethod 3: indexed() with for-in"
for (entry in colors.indexed()) {
println " colors[${entry.key}] = ${entry.value}"
}
// Practical: Find the index of the maximum value
def scores = [72, 95, 88, 61, 95, 83]
def maxScore = scores.max()
def maxIndices = []
for (i in 0..<scores.size()) {
if (scores[i] == maxScore) {
maxIndices << i
}
}
println "\nMax score ${maxScore} found at index: ${maxIndices}"
Output
Method 1: C-style index colors[0] = red colors[1] = green colors[2] = blue colors[3] = yellow colors[4] = purple Method 2: Range-based index colors[0] = red colors[1] = green colors[2] = blue colors[3] = yellow colors[4] = purple Method 3: indexed() with for-in colors[0] = red colors[1] = green colors[2] = blue colors[3] = yellow colors[4] = purple Max score 95 found at index: [1, 4]
The indexed() method returns a map of index-to-value pairs, which is elegant for for-in loops. For closure-based approaches, eachWithIndex() is the way to go.
Example 12: Infinite Loops and Guarded Exits
While infinite loops are generally bad, sometimes they’re exactly what you need – like reading from a stream until it’s exhausted, or implementing a retry mechanism. Groovy handles these just like Java.
Example 12: Infinite Loops and Guarded Exits
// Simulate a retry mechanism
def attempts = 0
def success = false
println "Simulating retry logic:"
for (;;) { // infinite loop
attempts++
println " Attempt ${attempts}..."
// Simulate success on 3rd attempt
if (attempts == 3) {
success = true
println " Success!"
break
}
println " Failed, retrying..."
}
println "Total attempts: ${attempts}"
// Practical: Simple number guessing game simulation
println "\nGuessing game simulation:"
def secret = 42
def guesses = [10, 25, 50, 42, 60]
def guessIndex = 0
for (;;) {
def guess = guesses[guessIndex++]
print " Guess: ${guess} → "
if (guess == secret) {
println "Correct! Found in ${guessIndex} guesses."
break
} else if (guess < secret) {
println "Too low!"
} else {
println "Too high!"
}
}
// While-style for loop
println "\nCollatz sequence starting from 12:"
def num = 12
print "${num}"
for (; num != 1;) {
num = (num % 2 == 0) ? num / 2 : num * 3 + 1
print " → ${num}"
}
println()
Output
Simulating retry logic: Attempt 1... Failed, retrying... Attempt 2... Failed, retrying... Attempt 3... Success! Total attempts: 3 Guessing game simulation: Guess: 10 → Too low! Guess: 25 → Too low! Guess: 50 → Too high! Guess: 42 → Correct! Found in 4 guesses. Collatz sequence starting from 12: 12 → 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1
The for (;;) syntax creates an infinite loop – always make sure you have a break condition or you’ll hang your program. The Collatz sequence example shows how you can use for in a while-loop style by omitting the initializer and update parts.
Example 13: Real-World – Processing Collections
Let’s bring everything together with a real-world scenario: processing a list of employee records with filtering, transformation, and aggregation.
Example 13: Real-World Collection Processing
// Employee data
def employees = [
[name: 'Alice', dept: 'Engineering', salary: 95000],
[name: 'Bob', dept: 'Marketing', salary: 72000],
[name: 'Charlie', dept: 'Engineering', salary: 88000],
[name: 'Diana', dept: 'HR', salary: 68000],
[name: 'Eve', dept: 'Engineering', salary: 110000],
[name: 'Frank', dept: 'Marketing', salary: 65000],
]
// Calculate department-wise statistics
def deptStats = [:]
for (emp in employees) {
def dept = emp.dept
if (!deptStats.containsKey(dept)) {
deptStats[dept] = [count: 0, total: 0, employees: []]
}
deptStats[dept].count++
deptStats[dept].total += emp.salary
deptStats[dept].employees << emp.name
}
println "Department Statistics:"
println "-" * 50
for (entry in deptStats) {
def dept = entry.key
def stats = entry.value
def avg = stats.total / stats.count
println " ${dept}:"
println " Employees: ${stats.employees.join(', ')}"
println " Count: ${stats.count}"
println " Avg Salary: \$${String.format('%,.0f', avg)}"
println()
}
// Find highest paid in each department
println "Highest Paid Per Department:"
for (entry in deptStats) {
def dept = entry.key
def highest = null
for (emp in employees) {
if (emp.dept == dept) {
if (highest == null || emp.salary > highest.salary) {
highest = emp
}
}
}
println " ${dept}: ${highest.name} (\$${String.format('%,d', highest.salary)})"
}
Output
Department Statistics:
--------------------------------------------------
Engineering:
Employees: Alice, Charlie, Eve
Count: 3
Avg Salary: $97,667
Marketing:
Employees: Bob, Frank
Count: 2
Avg Salary: $68,500
HR:
Employees: Diana
Count: 1
Avg Salary: $68,000
Highest Paid Per Department:
Engineering: Eve ($110,000)
Marketing: Bob ($72,000)
HR: Diana ($68,000)
This example shows for loops doing real work – grouping data, computing aggregates, and finding maximums. In production code you might use groupBy() and collectEntries() for these tasks, but understanding the for-loop approach gives you a solid foundation.
Example 14: Building Strings with For Loops
Building strings iteratively is a common task. Here are different patterns for constructing output with for loops.
Example 14: Building Strings
// Build a CSV row
def headers = ['Name', 'Age', 'City']
def values = ['Alice', 30, 'New York']
def csv = new StringBuilder()
for (i in 0..<headers.size()) {
if (i > 0) csv.append(',')
csv.append(values[i])
}
println "CSV: ${csv}"
// Build an HTML list
def items = ['Learn Groovy', 'Build a project', 'Deploy to production']
def html = new StringBuilder("<ul>\n")
for (item in items) {
html.append(" <li>${item}</li>\n")
}
html.append("</ul>")
println "\nHTML:\n${html}"
// Build a formatted table
def products = [
[name: 'Laptop', price: 999.99],
[name: 'Mouse', price: 29.99],
[name: 'Keyboard', price: 79.99],
]
def table = new StringBuilder()
table.append(String.format("%-12s %10s%n", "Product", "Price"))
table.append("-" * 23 + "\n")
for (product in products) {
table.append(String.format("%-12s %10s%n", product.name, "\$${product.price}"))
}
def total = 0
for (product in products) {
total += product.price
}
table.append("-" * 23 + "\n")
table.append(String.format("%-12s %10s", "TOTAL", "\$${total}"))
println "\n${table}"
Output
CSV: Alice,30,New York HTML: Learn Groovy Build a project Deploy to production Product Price ----------------------- Laptop $999.99 Mouse $29.99 Keyboard $79.99 ----------------------- TOTAL $1109.97
When building strings inside a loop, always use StringBuilder instead of string concatenation with +. Concatenation creates a new String object every iteration, which gets expensive fast. Alternatively, use collect() and join() for a more Groovy-idiomatic approach.
Edge Cases and Best Practices
Best Practices Summary
DO:
- Use
for (item in collection)instead of C-style loops when possible - Use ranges (
1..n) for numeric iteration – they’re cleaner and less error-prone - Use exclusive ranges (
0..<list.size()) for index-based iteration to avoid off-by-one errors - Use
forloops when you needbreakorcontinue - Use
StringBuilderfor string concatenation inside loops
DON’T:
- Modify a collection while iterating over it with
for-in– it throwsConcurrentModificationException - Use C-style for loops when a simple range or
each()would do - Forget that
breakandcontinuedon’t work insideeach()closures - Write infinite loops without a clear exit condition
Edge Case: Modifying During Iteration
// WRONG: Modifying list during for-in loop
def numbers = [1, 2, 3, 4, 5]
try {
for (n in numbers) {
if (n == 3) numbers.remove((Object) n)
}
} catch (ConcurrentModificationException e) {
println "ConcurrentModificationException caught!"
}
// CORRECT: Use removeAll or iterate over a copy
def numbers2 = [1, 2, 3, 4, 5]
def copy = new ArrayList(numbers2)
for (n in copy) {
if (n % 2 == 0) numbers2.remove((Object) n)
}
println "After removing evens: ${numbers2}"
// BEST: Use removeAll with a condition
def numbers3 = [1, 2, 3, 4, 5]
numbers3.removeAll { it % 2 == 0 }
println "After removeAll evens: ${numbers3}"
Output
ConcurrentModificationException caught! After removing evens: [1, 3, 5] After removeAll evens: [1, 3, 5]
The ConcurrentModificationException is one of the most common loop-related errors. Never add or remove elements from a collection while iterating over it. Either iterate over a copy, or use removeAll()/retainAll().
Performance Considerations
For most applications, the performance difference between loop types is negligible. But if you’re processing millions of items, here are some things to keep in mind:
Performance Comparison
def size = 1_000_000
// Measure C-style for loop
def start1 = System.nanoTime()
def sum1 = 0L
for (int i = 0; i < size; i++) {
sum1 += i
}
def time1 = (System.nanoTime() - start1) / 1_000_000
// Measure for-in with range
def start2 = System.nanoTime()
def sum2 = 0L
for (i in 0..<size) {
sum2 += i
}
def time2 = (System.nanoTime() - start2) / 1_000_000
// Measure each closure
def start3 = System.nanoTime()
def sum3 = 0L
(0..<size).each { sum3 += it }
def time3 = (System.nanoTime() - start3) / 1_000_000
println "Results (all sums equal: ${sum1 == sum2 && sum2 == sum3}):"
println " C-style for: ${time1}ms"
println " for-in range: ${time2}ms"
println " each closure: ${time3}ms"
Output
Results (all sums equal: true): C-style for: 12ms for-in range: 45ms each closure: 62ms
Note: These numbers vary by JVM, hardware, and warmup state. The C-style for loop is typically fastest because it avoids iterator and closure overhead. But unless you’re in a hot loop processing millions of items, readability matters more than a few milliseconds. Choose the loop style that makes your code clearest.
Common Pitfalls
Pitfall 1: Break Inside each() Doesn’t Work
Pitfall 1: break in each()
// WRONG: break inside each() causes compilation error
// [1, 2, 3, 4, 5].each {
// if (it == 3) break // ERROR!
// println it
// }
// CORRECT: Use a for loop when you need break
println "Using for loop with break:"
for (n in [1, 2, 3, 4, 5]) {
if (n == 3) break
println " ${n}"
}
// ALTERNATIVE: Use find() to stop at first match
println "\nUsing find() instead:"
def found = [1, 2, 3, 4, 5].find { it == 3 }
println " Found: ${found}"
Output
Using for loop with break: 1 2 Using find() instead: Found: 3
This is the most common pitfall for Groovy beginners. The each() method accepts a closure, and break/continue are language-level keywords that only work inside loops – not closures. If you need early termination, use a for loop or methods like find(), any(), or takeWhile().
Pitfall 2: Off-By-One with Ranges
Pitfall 2: Off-By-One Errors
def list = ['a', 'b', 'c', 'd', 'e']
// WRONG: Inclusive range goes out of bounds
// for (i in 0..list.size()) { // 0..5 includes index 5 → IndexOutOfBoundsException
// println list[i]
// }
// CORRECT: Use exclusive range
println "Correct iteration with exclusive range:"
for (i in 0..<list.size()) {
print "${list[i]} "
}
println()
// ALSO CORRECT: Just use for-in directly
println "Even better - iterate directly:"
for (item in list) {
print "${item} "
}
println()
Output
Correct iteration with exclusive range: a b c d e Even better - iterate directly: a b c d e
Remember: 0..list.size() is inclusive and includes the size itself (which is out of bounds). Use 0..<list.size() with the exclusive operator, or better yet, iterate directly with for (item in list).
Pitfall 3: Variable Scope in For Loops
Pitfall 3: Variable Scope
// The loop variable is scoped to the loop body
for (i in 1..3) {
def message = "Iteration ${i}"
println message
}
// println message // ERROR: message is not defined here
// In Groovy 5, loop variables are properly scoped (like Java)
for (x in 1..3) { }
// println "x after loop: ${x}" // ERROR in Groovy 5: x is not defined here
// (In Groovy 4 and earlier, x would still be accessible)
Output
Iteration 1 Iteration 2 Iteration 3 x after loop: 3
In Groovy scripts (not classes), the loop variable from for-in leaks into the surrounding scope. This is because scripts use a binding object. Inside a class method, the variable is properly scoped. Be aware of this behavior to avoid unexpected bugs.
Conclusion
We’ve covered every Groovy for loop variation you’ll encounter – from the classic C-style loop to the idiomatic for-in with ranges, lists, maps, and strings. We also looked at how for loops compare to each() and times(), and when to pick each approach.
The bottom line is this: use for (item in collection) for most iteration tasks. Use C-style for loops when you need precise index control. And use each() when you want functional-style code and don’t need break/continue.
Next up in this series, we’ll cover Groovy’s each() method – the closure-based iteration approach that most Groovy developers prefer for everyday looping.
Summary
- Groovy supports C-style
forloops and enhancedfor-inloops for (item in collection)is the idiomatic Groovy way to iterate- Ranges (
1..10,1..<10) replace verbose C-style counting loops breakandcontinuework in for loops but NOT ineach()closures- Never modify a collection while iterating over it – use a copy or
removeAll()
If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.
Up next: Groovy Each Loop – Closure-Based Iteration
Frequently Asked Questions
What is the difference between for and each() in Groovy?
The for loop is a language-level control structure that supports break and continue. The each() method is a GDK method that accepts a closure – it’s more concise but doesn’t support break or continue. Use for when you need early exit, and each() for simple iteration where you process every element.
How do I use a for loop with an index in Groovy?
You have several options: use a C-style loop (for (int i = 0; i < list.size(); i++)), use a range (for (i in 0.. }.
Can I use break inside a Groovy each() closure?
No. The break and continue keywords only work inside language-level loops (for, while, do-while). Since each() uses a closure, break will cause a compilation error. Use a for loop if you need break, or use find(), any(), or takeWhile() as functional alternatives.
What is a Groovy range and how do I use it in a for loop?
A Groovy range is a sequence of values with a start and end. Use 1..5 for inclusive (1,2,3,4,5) or 1..<5 for exclusive (1,2,3,4). In a for loop: for (i in 1..10) { println i }. Ranges work with integers, characters (‘a’..’z’), and any Comparable that implements next()/previous().
Which for loop is fastest in Groovy?
The C-style for loop (for (int i = 0; i < n; i++)) is typically fastest because it avoids iterator and closure overhead. The for-in loop is slightly slower due to iterator creation, and each() is slowest due to closure invocation. However, the difference is negligible for most applications – choose readability over micro-optimization.
Related Posts
Previous in Series: Groovy Collections Overview – Lists, Maps, and Sets
Next in Series: Groovy Each Loop – Closure-Based Iteration
Related Topics You Might Like:
- Groovy Each Loop – Closure-Based Iteration
- Groovy Times Loop – Repeat N Times
- Groovy Collections Overview – Lists, Maps, and Sets
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment