You need to seed five test users, retry a flaky call three times, or print a separator once per column. Java makes you write a counter loop for each of those. The Groovy times loop turns it into 5.times { }: no counter variable, no off-by-one. Here are 10 tested examples, plus upto(), downto(), and step() for when a plain count is not enough.
“Simple things should be simple, complex things should be possible.”
Alan Kay
Last Updated: July 2026 | Tested on: Groovy 5.0.6, Java 17+ | Difficulty: Beginner | Reading Time: 14 minutes
Ever written a for loop just to repeat something a fixed number of times and thought, “There must be a cleaner way”? The groovy times loop via the times() method lets you write 5.times { } instead of for (int i = 0; i < 5; i++) – no counter variable, no off-by-one errors, no boilerplate.
Instead of writing for (int i = 0; i < 5; i++), you simply write 5.times { }. That’s it. No counter variable to manage, no off-by-one errors, no boilerplate. It’s one of those Groovy features that makes you wonder why every language doesn’t have it.
In this post, we’ll explore the times() method with 10 practical, tested examples. We’ll also cover the closely related upto(), downto(), and step() methods. If you’re coming from our Groovy for loop or Groovy each loop guides, you’ll see how times() fits into the bigger picture of Groovy iteration.
The diagram below is a quick decision guide: answer a couple of questions about what you need, whether you require a break or a result list, a fixed count or a specific range, counting up, down, or with a custom step, and it points you to the right method before you write a single line:
Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.
Table of Contents
What Is the Groovy times() Method?
The times() method is defined on the Integer class by the Groovy Development Kit (GDK). It executes a closure a specified number of times. According to the official Groovy GDK documentation, the method signature is:
times() Method Signature
public void times(Closure closure)
The closure receives an implicit parameter it – the current iteration index starting from 0. So 5.times { } runs the closure 5 times, with it going from 0 to 4.
Key Points:
- Called directly on any
Integervalue - Executes the closure exactly N times
- Index starts at 0, ends at N-1
- Returns
void– it’s purely for side effects - Calling
0.times { }does nothing (no error) - Negative numbers also do nothing (no error)
Syntax and Basic Usage
Basic times() Syntax
// Simplest form - no index needed
3.times {
println "Hello!"
}
// With implicit index (it)
3.times {
println "Iteration: ${it}"
}
// With named parameter
3.times { index ->
println "Step ${index + 1} of 3"
}
Output
Hello! Hello! Hello! Iteration: 0 Iteration: 1 Iteration: 2 Step 1 of 3 Step 2 of 3 Step 3 of 3
Notice the three variations. When you don’t care about the index, just use an empty closure parameter. When you want the index, either use the implicit it or name it explicitly for better readability.
Quick Reference Table
| Method | Range | Description | Example |
|---|---|---|---|
n.times{} | 0 to n-1 | Repeat n times | 5.times { print it } → 01234 |
a.upto(b){} | a to b (inclusive) | Count up from a to b | 1.upto(5) { print it } → 12345 |
b.downto(a){} | b to a (inclusive) | Count down from b to a | 5.downto(1) { print it } → 54321 |
a.step(b,s){} | a to b (exclusive), step s | Count with custom step | 0.step(10,2) { print it } → 02468 |
times() vs for Loop
If you’ve been using Groovy for loops, you might wonder when to use times() instead. Here’s a direct comparison:
times() vs for Loop
// Java-style for loop
println "=== Java-style for ==="
for (int i = 0; i < 5; i++) {
print "${i} "
}
println()
// Groovy range for loop
println "=== Range for ==="
for (i in 0..4) {
print "${i} "
}
println()
// times() - cleanest
println "=== times() ==="
5.times { print "${it} " }
println()
Output
=== Java-style for === 0 1 2 3 4 === Range for === 0 1 2 3 4 === times() === 0 1 2 3 4
All three produce the same result, but times() is the most concise. Use times() when you just need to repeat something N times. Use a for loop when you need break or continue – times() doesn’t support those since it uses a closure. Use each() when you’re iterating over a collection.
10 Practical Groovy times Loop Examples
Example 1: Basic times() – Repeat a Message
What we’re doing: Running a simple action multiple times without caring about the index.
Example 1: Basic times()
// Print a message 5 times
5.times {
println "Groovy is awesome!"
}
Output
Groovy is awesome! Groovy is awesome! Groovy is awesome! Groovy is awesome! Groovy is awesome!
What happened here: The closure runs exactly 5 times. We didn’t use it at all – sometimes you just want to repeat an action without tracking which iteration you’re on. This is the simplest form of the Groovy times loop.
Example 2: times() with the Index Variable (it)
What we’re doing: Using the implicit it parameter to access the iteration index.
Example 2: Using the Index
// Print index values
println "=== Index values ==="
5.times {
println "Index: ${it}"
}
// Use index in calculations
println "\n=== Squares ==="
6.times {
println "${it} squared = ${it * it}"
}
Output
=== Index values === Index: 0 Index: 1 Index: 2 Index: 3 Index: 4 === Squares === 0 squared = 0 1 squared = 1 2 squared = 4 3 squared = 9 4 squared = 16 5 squared = 25
What happened here: The implicit parameter it holds the current iteration index, starting at 0. This is a Groovy closure convention – when a closure has a single parameter you don’t name, it’s automatically called it.
Example 3: times() with a Named Parameter
What we’re doing: Giving the index a meaningful name for better readability.
Example 3: Named Parameter
// Named parameter makes code self-documenting
println "=== Countdown ==="
5.times { count ->
println "T-minus ${5 - count}..."
}
println "Liftoff!"
println "\n=== Player Scores ==="
4.times { playerIndex ->
def score = (playerIndex + 1) * 100
println "Player ${playerIndex + 1}: ${score} points"
}
Output
=== Countdown === T-minus 5... T-minus 4... T-minus 3... T-minus 2... T-minus 1... Liftoff! === Player Scores === Player 1: 100 points Player 2: 200 points Player 3: 300 points Player 4: 400 points
What happened here: Instead of using the generic it, we named the parameter count and playerIndex. This is especially useful when the closure body is more than a line or two – named parameters make the intent crystal clear.
Example 4: Building a Collection with times()
What we’re doing: Using times() to populate a list with generated data.
Example 4: Building Collections
// Build a list of items
def items = []
5.times { items << "Item-${it + 1}" }
println "Items: ${items}"
// Build a list of maps
def users = []
3.times { i ->
users << [id: i + 1, name: "User_${i + 1}", active: true]
}
println "Users: ${users}"
// Build a matrix (list of lists)
def matrix = []
3.times { row ->
def cols = []
3.times { col ->
cols << (row * 3 + col + 1)
}
matrix << cols
}
println "Matrix: ${matrix}"
Output
Items: [Item-1, Item-2, Item-3, Item-4, Item-5] Users: [[id:1, name:User_1, active:true], [id:2, name:User_2, active:true], [id:3, name:User_3, active:true]] Matrix: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
What happened here: Since times() returns void, we can’t use it directly for collection building like collect(). Instead, we declare the collection outside and use the left-shift operator (<<) to append items inside the closure. The nested times() call for the matrix shows you can nest these loops just like nested for loops.
Example 5: Test Data Generation with times()
What we’re doing: Generating test data – one of the most common real-world uses of times().
Example 5: Test Data Generation
def random = new Random(42) // fixed seed for reproducible output
// Generate test users
def testUsers = []
10.times { i ->
testUsers << [
id : i + 1,
username : "testuser_${i + 1}",
email : "user${i + 1}@test.com",
age : 20 + random.nextInt(40),
role : i < 2 ? 'admin' : 'user'
]
}
println "Generated ${testUsers.size()} test users:"
testUsers.each { user ->
println " ${user.id}. ${user.username} (${user.email}) - age: ${user.age}, role: ${user.role}"
}
Output
Generated 10 test users: 1. testuser_1 (user1@test.com) - age: 30, role: admin 2. testuser_2 (user2@test.com) - age: 23, role: admin 3. testuser_3 (user3@test.com) - age: 28, role: user 4. testuser_4 (user4@test.com) - age: 24, role: user 5. testuser_5 (user5@test.com) - age: 30, role: user 6. testuser_6 (user6@test.com) - age: 25, role: user 7. testuser_7 (user7@test.com) - age: 45, role: user 8. testuser_8 (user8@test.com) - age: 58, role: user 9. testuser_9 (user9@test.com) - age: 59, role: user 10. testuser_10 (user10@test.com) - age: 33, role: user
What happened here: We used 10.times to generate a batch of test user maps. The index i drives the unique IDs, usernames, and email addresses. The first two users get the admin role while the rest are regular users. This pattern is incredibly common in unit tests and Spock specifications.
Example 6: Retry Logic with times()
What we’re doing: Implementing a simple retry mechanism – another practical use case for times().
Example 6: Retry Logic
// Simulate a flaky operation that fails sometimes
def random = new Random(42)
def success = false
def maxRetries = 5
maxRetries.times { attempt ->
if (!success) {
println "Attempt ${attempt + 1} of ${maxRetries}..."
// Simulate: 40% chance of success
if (random.nextInt(100) < 40) {
println " ✓ Operation succeeded on attempt ${attempt + 1}!"
success = true
} else {
println " ✗ Failed. ${attempt < maxRetries - 1 ? 'Retrying...' : 'No more retries.'}"
}
}
}
if (!success) {
println "All ${maxRetries} attempts failed."
}
Output
Attempt 1 of 5... ✓ Operation succeeded on attempt 1!
What happened here: We used times() to cap the maximum number of retry attempts. Since times() doesn’t support break, we used a success flag to skip the remaining iterations once the operation passes. In production code, you’d typically add a Thread.sleep() between retries for backoff.
Example 7: upto() – Count Up Between Two Numbers
What we’re doing: Using upto() to iterate from a starting number to an ending number (both inclusive).
Example 7: upto()
// Count from 1 to 5
print "1 to 5: "
1.upto(5) { print "${it} " }
println()
// Multiplication table for 7
println "\n7 times table:"
1.upto(10) { num ->
println " 7 x ${num} = ${7 * num}"
}
// ASCII characters from A to Z
print "\nAlphabet: "
65.upto(90) { print "${(char)it}" }
println()
Output
1 to 5: 1 2 3 4 5 7 times table: 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 Alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ
What happened here: Unlike times() which always starts at 0, upto() lets you specify both the start and end values. Both endpoints are inclusive, so 1.upto(5) gives you 1, 2, 3, 4, 5. This is often cleaner than times() when your logic is 1-based rather than 0-based.
Example 8: downto() – Count Down
What we’re doing: Using downto() to iterate in reverse – from a higher number to a lower one.
Example 8: downto()
// Simple countdown
print "Countdown: "
10.downto(1) { print "${it} " }
println("Go!")
// Reverse a number range
println "\nFloor selector (top to ground):"
5.downto(0) { floor ->
def label = floor == 0 ? "Ground" : "Floor ${floor}"
println " → ${label}"
}
// Decreasing powers of 2
println "\nPowers of 2 (descending):"
10.downto(0) { exp ->
println " 2^${exp} = ${Math.pow(2, exp).intValue()}"
}
Output
Countdown: 10 9 8 7 6 5 4 3 2 1 Go! Floor selector (top to ground): → Floor 5 → Floor 4 → Floor 3 → Floor 2 → Floor 1 → Ground Powers of 2 (descending): 2^10 = 1024 2^9 = 512 2^8 = 256 2^7 = 128 2^6 = 64 2^5 = 32 2^4 = 16 2^3 = 8 2^2 = 4 2^1 = 2 2^0 = 1
What happened here: downto() is the mirror image of upto(). It iterates from the calling integer down to the argument, both inclusive. This is much cleaner than writing for (i = 10; i >= 1; i--).
Example 9: step() – Custom Step Size
What we’re doing: Using step() to iterate with a custom increment (or decrement).
Example 9: step()
// Even numbers from 0 to 20
print "Evens: "
0.step(21, 2) { print "${it} " }
println()
// Count by 5s
print "By 5s: "
0.step(51, 5) { print "${it} " }
println()
// Step backwards
print "Reverse by 3: "
30.step(-1, -3) { print "${it} " }
println()
// Step through hours of the day
println "\nWork hours:"
9.step(18, 1) { hour ->
def period = hour < 12 ? "AM" : "PM"
def displayHour = hour > 12 ? hour - 12 : hour
println " ${displayHour}:00 ${period}"
}
Output
Evens: 0 2 4 6 8 10 12 14 16 18 20 By 5s: 0 5 10 15 20 25 30 35 40 45 50 Reverse by 3: 30 27 24 21 18 15 12 9 6 3 0 Work hours: 9:00 AM 10:00 AM 11:00 AM 12:00 PM 1:00 PM 2:00 PM 3:00 PM 4:00 PM 5:00 PM
What happened here: The step() method takes two parameters: the end value (exclusive) and the step size. A positive step counts up, a negative step counts down. Notice that the end value is exclusive – unlike upto() and downto() which are inclusive.
Example 10: Combining times() with Other Methods
What we’re doing: Using times() alongside other Groovy collection methods for more complex tasks.
Example 10: Combining Methods
// Build and process a list
def scores = []
10.times { scores << new Random(it).nextInt(100) }
println "Scores: ${scores}"
println "Average: ${scores.sum() / scores.size()}"
println "Max: ${scores.max()}, Min: ${scores.min()}"
// Create a formatted table
println "\n+------+--------+--------+"
println "| Num | Square | Cube |"
println "+------+--------+--------+"
8.times { n ->
def num = n + 1
printf "| %4d | %6d | %6d |\n", num, num * num, num * num * num
}
println "+------+--------+--------+"
// String repetition vs times()
def separator = "-" * 40
println "\n${separator}"
println " times() can do what '*' can't:"
println separator
3.times { i ->
println " ${' ' * (i * 2)}Level ${i}"
}
Output
Scores: [60, 85, 8, 34, 62, 87, 11, 36, 64, 89]
Average: 53.6
Max: 89, Min: 8
+------+--------+--------+
| Num | Square | Cube |
+------+--------+--------+
| 1 | 1 | 1 |
| 2 | 4 | 8 |
| 3 | 9 | 27 |
| 4 | 16 | 64 |
| 5 | 25 | 125 |
| 6 | 36 | 216 |
| 7 | 49 | 343 |
| 8 | 64 | 512 |
+------+--------+--------+
----------------------------------------
times() can do what '*' can't:
----------------------------------------
Level 0
Level 1
Level 2
What happened here: We combined times() with collection methods like sum(), max(), and min(). We also used printf for formatted output and the string multiplication operator ("x" * n) alongside times(). The string * operator repeats a string, while times() repeats an action – they complement each other well.
Related Methods: upto(), downto(), and step()
We saw upto(), downto(), and step() in the examples above. Let’s do a quick side-by-side comparison to lock in when to use each one:
Comparison: All Four Methods
// times() - repeat N times, 0-indexed
print "times: "
5.times { print "${it} " }
println()
// upto() - inclusive range going up
print "upto: "
1.upto(5) { print "${it} " }
println()
// downto() - inclusive range going down
print "downto: "
5.downto(1) { print "${it} " }
println()
// step() - custom step, exclusive end
print "step: "
0.step(10, 2) { print "${it} " }
println()
Output
times: 0 1 2 3 4 upto: 1 2 3 4 5 downto: 5 4 3 2 1 step: 0 2 4 6 8
When to use which:
- times() – When you just need to repeat something N times and the 0-based index is fine.
- upto() – When you want a specific start and end (1-based ranges, counting from X to Y).
- downto() – When you need to count backwards (countdowns, reverse processing).
- step() – When you need a custom increment like every 2nd, 5th, or 10th number.
All four methods are documented in the GDK Integer class reference.
Real-World Use Cases
Batch Processing
Processing data in fixed-size batches is a natural fit for times():
Batch Processing
def allRecords = (1..47).collect { "Record-${it}" }
def batchSize = 10
def totalBatches = Math.ceil(allRecords.size() / batchSize).intValue()
println "Total records: ${allRecords.size()}"
println "Batch size: ${batchSize}"
println "Total batches: ${totalBatches}"
println()
totalBatches.times { batchNum ->
def start = batchNum * batchSize
def end = Math.min(start + batchSize, allRecords.size())
def batch = allRecords[start..<end]
println "Batch ${batchNum + 1}: Processing ${batch.size()} records (${batch.first()} to ${batch.last()})"
}
Output
Total records: 47 Batch size: 10 Total batches: 5 Batch 1: Processing 10 records (Record-1 to Record-10) Batch 2: Processing 10 records (Record-11 to Record-20) Batch 3: Processing 10 records (Record-21 to Record-30) Batch 4: Processing 10 records (Record-31 to Record-40) Batch 5: Processing 7 records (Record-41 to Record-47)
Stress Testing
Running stress tests or load simulations with a fixed number of iterations:
Stress Testing
def random = new Random(42)
def responseTimes = []
// Simulate 20 API calls
20.times { i ->
def responseTime = 50 + random.nextInt(200) // 50-250ms
responseTimes << responseTime
}
// Analyze results
def avg = (responseTimes.sum() / responseTimes.size()).round(1)
def sorted = responseTimes.sort()
def p50 = sorted[(sorted.size() * 0.5).intValue()]
def p95 = sorted[(sorted.size() * 0.95).intValue()]
def p99 = sorted[(sorted.size() * 0.99).intValue()]
println "Stress Test Results (20 requests):"
println " Average: ${avg}ms"
println " Min: ${responseTimes.min()}ms"
println " Max: ${responseTimes.max()}ms"
println " P50: ${p50}ms"
println " P95: ${p95}ms"
println " P99: ${p99}ms"
println " Under 200ms: ${responseTimes.count { it < 200 }} / ${responseTimes.size()}"
Output
Stress Test Results (20 requests): Average: 154.7ms Min: 59ms Max: 232ms P50: 155ms P95: 232ms P99: 232ms Under 200ms: 16 / 20
Progress Bar / Animation
Building text-based progress indicators:
Progress Bar
def totalSteps = 20
// Build a progress bar
totalSteps.times { step ->
def progress = ((step + 1) / totalSteps * 100).intValue()
def filled = (step + 1)
def empty = totalSteps - filled
def bar = "${'█' * filled}${'░' * empty}"
// In a real app, you'd use \r for overwriting
if ((step + 1) % 5 == 0 || step == 0) {
println "[${bar}] ${progress}%"
}
}
println "Done!"
Output
[█░░░░░░░░░░░░░░░░░░░] 5% [█████░░░░░░░░░░░░░░░] 25% [██████████░░░░░░░░░░] 50% [███████████████░░░░░] 75% [████████████████████] 100% Done!
Edge Cases and Best Practices
Edge Cases
// Zero times - does nothing, no error
print "Zero times: "
0.times { print "x" }
println "(nothing printed)"
// Negative number - also does nothing, no error
print "Negative: "
(-5).times { print "x" }
println "(nothing printed)"
// One time - runs exactly once
print "One time: "
1.times { print "runs once " }
println()
// Variable-driven - the number can come from anywhere
def userInput = 3
print "Variable (${userInput}): "
userInput.times { print "* " }
println()
// Large number - be careful with big values
def start = System.currentTimeMillis()
1_000_000.times { /* do nothing */ }
def elapsed = System.currentTimeMillis() - start
println "1 million empty iterations: ${elapsed}ms"
Output
Zero times: (nothing printed) Negative: (nothing printed) One time: runs once Variable (3): * * * 1 million empty iterations: 163ms
One caveat on that last line: the millisecond count depends on your machine and JVM warmup, so expect a different number when you run it.
Best Practices:
- Use times() for simple repetition – when you just need to do something N times
- Name the parameter when the closure is multi-line –
{ index -> ... }is clearer than{ ... it ... } - Use upto() for 1-based iteration –
1.upto(n)is better thann.times { it + 1 } - Prefer (0..<n).collect{} over times() for building lists – since times() returns void, collect is more functional
- Don’t use times() when you need break – use a traditional for loop instead
- Validate the number before calling times() – negative values won’t crash, but they silently do nothing, which can hide bugs
Performance Considerations
A common question is whether times() is slower than a traditional for loop. Let’s measure:
Performance Comparison
def iterations = 10_000_000
// Traditional for loop
def start1 = System.nanoTime()
long sum1 = 0
for (int i = 0; i < iterations; i++) {
sum1 += i
}
def time1 = (System.nanoTime() - start1) / 1_000_000
// times() method
def start2 = System.nanoTime()
long sum2 = 0
iterations.times { sum2 += it }
def time2 = (System.nanoTime() - start2) / 1_000_000
println "10 million iterations:"
println " for loop: ${time1}ms (sum=${sum1})"
println " times(): ${time2}ms (sum=${sum2})"
println " Overhead: ${time2 - time1}ms"
Output
10 million iterations: for loop: 406.474ms (sum=49999995000000) times(): 11719.4196ms (sum=49999995000000) Overhead: 11312.9456ms
Yes, times() is slower than a raw for loop because it creates and invokes a closure on each iteration. And that gap grew a lot in Groovy 5: on the same machine, this exact script finished the times() loop in about 0.7 seconds on Groovy 4.0.24 but needed over 11 seconds on Groovy 5.0.6 (the version at the time of writing). Exact timings vary by machine and run, but the pattern held every time we tested it. For realistic workloads – hundreds or thousands of iterations with actual logic inside – the closure overhead still won’t matter. Just avoid dynamic times() for tight loops with millions of trivial iterations.
Rule of Thumb: If readability matters more than nanoseconds (it usually does), use
times(). If you’re writing a hot inner loop processing millions of elements, benchmark first.
Common Pitfalls
Pitfall 1: Trying to Use break or continue
Pitfall: No break/continue
// This WON'T compile:
// 10.times {
// if (it == 5) break // ERROR: break not allowed in closure
// }
// Workaround 1: Use a flag
def stop = false
10.times {
if (!stop) {
if (it == 5) {
stop = true
return // 'return' exits current closure iteration, not the loop
}
println "Processing ${it}"
}
}
// Workaround 2: Use a for loop instead
println "\nWith for loop:"
for (i in 0..9) {
if (i == 5) break
println "Processing ${i}"
}
Output
Processing 0 Processing 1 Processing 2 Processing 3 Processing 4 With for loop: Processing 0 Processing 1 Processing 2 Processing 3 Processing 4
Since times() uses a closure, you can’t use break or continue. The return keyword inside a closure only exits that single closure call – it doesn’t stop the loop. If you genuinely need early termination, use a for loop instead.
Pitfall 2: Assuming times() Returns a Value
Pitfall: void Return
// This won't work as expected:
def result = 5.times { it * 2 }
println "Result: ${result}" // null - times() returns void
// Instead, use collect with a range:
def result2 = (0..4).collect { it * 2 }
println "Result2: ${result2}"
Output
Result: null Result2: [0, 2, 4, 6, 8]
Remember: times() is for side effects (printing, modifying external state, sending requests). If you need to transform data, use (0..<n).collect { } instead – it returns a list.
Pitfall 3: Off-by-One with upto() vs step()
Pitfall: Inclusive vs Exclusive
// upto() is INCLUSIVE
print "upto(5): "
1.upto(5) { print "${it} " }
println() // 1 2 3 4 5 - includes 5
// step() is EXCLUSIVE
print "step(5): "
1.step(5, 1) { print "${it} " }
println() // 1 2 3 4 - does NOT include 5
// To match upto(5) with step(), use 6
print "step(6): "
1.step(6, 1) { print "${it} " }
println() // 1 2 3 4 5 - now includes 5
Output
upto(5): 1 2 3 4 5 step(5): 1 2 3 4 step(6): 1 2 3 4 5
This is one of the most common gotchas. upto() and downto() include the endpoint, while step() excludes it – just like Python’s range().
Conclusion
The Groovy times() loop is one of those small features that makes Groovy feel so much more expressive than Java. Instead of writing boilerplate for (int i = 0; i < n; i++), you just say n.times { }. It reads like English, eliminates off-by-one errors, and keeps your code focused on what matters – the action, not the loop mechanics.
We covered 10 practical examples, from basic repetition to test data generation, retry logic, batch processing, and stress testing. We also explored the related upto(), downto(), and step() methods that give you more control over your iteration ranges.
The bottom line for the Groovy times loop: use times() for simple N-time repetition, upto()/downto() for specific ranges, and step() for custom increments. And remember – if you need break or a return value, reach for a for loop or collect() instead.
Summary
n.times { }repeats a closure exactly n times with index 0 to n-1- The implicit parameter
itholds the current index – name it explicitly for clarity upto()anddownto()are inclusive on both ends;step()is exclusive on the endtimes()returns void – use(0..<n).collect{}when you need a result list- No
break/continuein closures – use a for loop if you need early exit
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 Switch Statement – Pattern Matching Made Easy
Frequently Asked Questions
What does times() do in Groovy?
The times() method is called on an Integer and executes a closure that many times. For example, 5.times { println it } runs the closure 5 times, printing 0 through 4. It’s a cleaner alternative to writing a traditional for loop when you just need to repeat an action a fixed number of times, which is why the groovy times loop shows up constantly in test fixtures and demo scripts.
Does Groovy times() start at 0 or 1?
The times() method always starts at 0 and ends at n-1. So 5.times {} iterates with values 0, 1, 2, 3, 4. If you need 1-based iteration, use 1.upto(5) {} instead, which gives you 1, 2, 3, 4, 5.
Can I use break inside Groovy times()?
No. Since times() uses a closure, the break keyword is not allowed. Using return inside the closure only exits that single iteration – it does not stop the loop. If you need early termination, use a traditional for loop or a flag variable to skip remaining iterations.
What is the difference between times() and upto() in Groovy?
times() is called on a single integer and iterates from 0 to n-1. upto() is called with a start and end value, iterating inclusively between them. Use times() for 0-based repetition (like 5.times{}) and upto() for specific ranges (like 1.upto(10){}). Additionally, step() lets you iterate with a custom increment.
Is Groovy times() slower than a for loop?
Yes, times() has real overhead because it invokes a closure on each iteration. In our benchmark on Groovy 5.0.6, 10 million trivial iterations took over 11 seconds with times() versus under half a second with a raw for loop; Groovy 4.x showed a much smaller gap. For realistic workloads with actual logic inside the loop, the difference is negligible. Prioritize readability – only optimize if profiling shows it matters.
Interview Questions on the Groovy times Loop
“Interviewers rarely ask for definitions. They ask what happens in situations like these.”
Q: A retry helper is written as 3.times { if (!done) { done = attempt() } }. It works, but what would you flag in review?
It always runs all three iterations. times() has no early exit, so after the first successful attempt the closure still gets invoked and just skips work by checking the flag. That is harmless here but it hides the intent and scales badly, say, with 50 retries and a sleep between attempts. A for loop with break, or a while (!done && attempts < 3), states the exit condition directly. The point the interviewer wants: closures cannot break, so times() is only right when you truly want every iteration.
Q: A teammate writes def squares = 5.times { it * it } and gets null. Why, and what is the idiomatic fix?
times() is a side-effect method; it discards whatever the closure returns and itself returns null. Nothing was collected. When you want values out, iterate something that produces them: (0..<5).collect { it * it } gives [0, 1, 4, 9, 16]. This mirrors the each() versus collect() split: repeat-for-effect versus transform-into-result. Interviewers like hearing you classify the methods by what they return, because that is how you avoid this whole family of bugs.
Q: In a Jenkins pipeline, env.RETRIES.times { ... } throws MissingMethodException even though RETRIES is set to 3. What is wrong?
Environment variables are Strings, and times() lives on Number, so you are calling a method the String class does not have. The message even says it: no signature of method times for class java.lang.String. Convert first: env.RETRIES.toInteger().times { ... }. The related trap is a config lookup returning null; unlike null.each, which Groovy silently treats as empty, null.times throws a NullPointerException. So validate both the type and the presence of externally supplied counts before looping on them.
Q: A batch splitter computes def fullBatches = total.intdiv(batchSize); fullBatches.times { ... }. One day the job processes nothing and no error appears anywhere. What happened?
The computed count came out as zero or negative, and times() on a non-positive number simply runs the closure zero times: no exception, no log, nothing. Negative counts do not throw. That silence is the danger with computed counts, whether it is an empty input file or a batch size larger than the total. The defensive habit is an assertion or an explicit log line before the loop, something like assert fullBatches > 0 in a script, so a data problem fails loudly instead of shipping an empty run.
Q: You need to iterate from 10 down to 1 and someone suggests 10.times { println 10 - it }. What cleaner options does Groovy give you, and what are their boundary rules?
The arithmetic works, but it makes the reader do math to see the sequence. 10.downto(1) { println it } says it outright, and upto() is its ascending twin; both are inclusive on both ends. For custom increments there is 0.step(100, 10) { }, and its end bound is exclusive, which is the boundary asymmetry people forget. Reverse ranges work too: (10..1).each { }. Picking the method whose bounds match the requirement, instead of adjusting with plus-one arithmetic, is exactly what these methods are for.
Related Posts
Previous in Series: Groovy each() Loop – Iterate Collections the Groovy Way
Next in Series: Groovy Switch Statement – Pattern Matching Made Easy
Related Topics You Might Like:
- Groovy For Loop – Classic and Range-Based Iteration
- Groovy each() Loop – Iterate Collections the Groovy Way
- Groovy Switch Statement – Pattern Matching Made Easy
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment