FizzBuzz with Groovy and Emojis
Published: 2026-06-14 08:00AM
Introduction
FizzBuzz is the little interview
chestnut: print the numbers 1 to n, but say Fizz for multiples of
three, Buzz for multiples of five, and FizzBuzz for multiples of both.
It is small enough to fit in a tweet and yet — as a decade of blog posts
attests — roomy enough to show off whatever language feature you happen to
be fond of this week.
This post is unapologetically in that spirit. It is not a hunt for the one true FizzBuzz; it is a tour of several genuinely different ways to write it in Groovy 6, each with its own flavour. To keep them comparable we render them all with the same emoji instead of words:
| Rule | Word | Emoji |
|---|---|---|
|
Fizz |
🥤 |
|
Buzz |
🐝 |
|
FizzBuzz |
🥤🐝 |
The emoji idea is borrowed, with thanks, from Don Raab’s
Ternary,
Predicate, and Pattern Matching for FizzBuzz with Java 26, which dresses up
its Java variants the same way. It is also where the emoji appear in the
groovy-verify
project, one of whose examples we revisit below.
The starting point for this post was a reader’s first attempt, which already shows how compact Groovy can be:
val result = (1..20).collect { each ->
switch (each) {
case { each % 15 == 0 } -> "🥤🐝"
case { each % 3 == 0 } -> "🥤"
case { each % 5 == 0 } -> "🐝"
default -> each
}
}
println result.join(' ')
// 1 2 🥤 4 🐝 🥤 7 8 🥤 🐝 11 🥤 13 14 🥤🐝 16 17 🥤 19 🐝
Every runnable example maps the same range (1..20) and asserts the same
canonical output, so the shapes are directly comparable:
1 2 🥤 4 🐝 🥤 7 8 🥤 🐝 11 🥤 13 14 🥤🐝 16 17 🥤 19 🐝
A companion project at
groovy-fizzbuzz holds
runnable versions of every example, resolving Groovy 6.0.0-SNAPSHOT from the
ASF snapshot repository.
The idiomatic switch
Groovy’s switch is an expression, and its case labels can be closures used
as predicates — applied to the switch subject, first match wins. This is the
cleaned-up form of the opening attempt, and the same shape the
GPars meets Virtual
Threads post used for the word version:
def fizzbuzz = { int n ->
switch (n) {
case { it % 15 == 0 } -> '🥤🐝'
case { it % 3 == 0 } -> '🥤'
case { it % 5 == 0 } -> '🐝'
default -> n.toString()
}
}
assert (1..20).collect { fizzbuzz(it) }.join(' ') ==
'1 2 🥤 4 🐝 🥤 7 8 🥤 🐝 11 🥤 13 14 🥤🐝 16 17 🥤 19 🐝'
The nested ternary
The most concise of Don Raab’s Java variants is a nested ternary, and it translates to Groovy almost character for character. It reads top to bottom — 15, then 3, then 5 — as one expression with no statements:
def fizzbuzz = { int n ->
n % 15 == 0 ? '🥤🐝'
: n % 3 == 0 ? '🥤'
: n % 5 == 0 ? '🐝'
: n.toString()
}
This particular spelling earns its keep later: it is exactly the spec(n) the
verified version proves correct.
Building the string
Here is a trick that drops the % 15 special case entirely. Glue the 🥤 and 🐝
fragments together; when a fragment doesn’t apply it contributes the empty
string and simply vanishes. The FizzBuzz case falls out for free when both
are present. Groovy’s Elvis operator (?:) then supplies the number whenever
the glued string is empty (and therefore falsy):
def fizzbuzz = { int n ->
def s = (n % 3 == 0 ? '🥤' : '') + (n % 5 == 0 ? '🐝' : '')
s ?: n.toString()
}
Three rules expressed with two tests — the third emerges from the other two.
The most elegant FizzBuzz: cycles
The well-travelled Reddit thread on the most elegant implementation of FizzBuzz makes a lovely observation: you don’t need a divisibility test in the mapping at all. The 🥤 pattern repeats with period three and the 🐝 pattern with period five; lay them down by position and glue them. The classic Haskell rendition zips two infinite lazy cycles:
zipWith (++) (cycle ["","","🥤"]) (cycle ["","","","","🐝"])
That yields the fizz/buzz fragments, but a blank wherever a plain number belongs.
The full FizzBuzz zips those against the naturals [1..], keeping the number
whenever the glued string is empty:
zipWith (\n s -> if null s then show n else s)
[1..]
(zipWith (++) (cycle ["","","🥤"]) (cycle ["","","","","🐝"]))
Indexing two short period arrays by (n - 1) % period is the same idea without
the laziness, and it is this second form that Groovy matches — the Elvis
?: n.toString() does the job of if null s then show n:
def fizz = ['', '', '🥤'] // period 3
def buzz = ['', '', '', '', '🐝'] // period 5
def fizzbuzz = { int n ->
(fizz[(n - 1) % 3] + buzz[(n - 1) % 5]) ?: n.toString()
}
Arithmetic has moved out of the conditionals and into the indexing — the divisibility is encoded in the lengths of the two patterns.
Groovy 6 can also do the actually lazy version, and now the match with that
second Haskell form is almost line for line: repeat() is cycle, zip is the
inner zipWith (++), indexed(1) supplies the [1..], collecting is the
outer zipWith, and s[0] + s[1] ?: n is the combine that keeps the number
when the glue is empty. Nothing is computed until take(20) pulls a prefix:
def fizz = ['', '', '🥤'].iterator().repeat()
def buzz = ['', '', '', '', '🐝'].iterator().repeat()
def result = fizz.zip(buzz)
.indexed(1)
.collecting { n, s -> s[0] + s[1] ?: n } // glue the fragments; Elvis → the number
.take(20)
.toList()
Same trick, now genuinely infinite — the two streams cycle without end and we simply stop pulling after twenty.
FizzBuzz as a truth table
Step back and FizzBuzz is just a function of two yes/no questions — "divisible by three?" and "divisible by five?" — which makes it a function of two bits, and a function of two bits fits in a 2×2 table. The most literal encoding of the problem there is: index the table by those two answers.
def fb = [['', '🥤'], // not ÷5: [number, 🥤 ]
['🐝', '🥤🐝']] // ÷5: [🐝, 🥤🐝]
def fizzbuzz = { int n ->
fb[n % 5 == 0 ? 1 : 0][n % 3 == 0 ? 1 : 0] ?: n.toString()
}
Where cycles indexed by position in a period, this indexes by the two
conditions — the same data laid out along different axes. The top-left cell is
empty, so Elvis supplies the number, exactly as before.
The same table reads naturally as a switch, too. Groovy matches a String
case label by equality, so stringifying the pair of bits gives the four rows
something concrete to match:
switch ([n % 3 == 0, n % 5 == 0].toString()) {
case '[true, true]' -> '🥤🐝'
case '[true, false]' -> '🥤'
case '[false, true]' -> '🐝'
default -> n
}
The .toString() is the tell: switch wants a value to compare, so we hand it
a string key. Groovy 7 will let us match the structure itself — we come back to
that at the end.
FizzBuzz as data: a predicate list
Don Raab’s most readable variant uses Eclipse Collections'
CaseFunction: an ordered list of
(predicate, result) cases, first match wins, with a default. The same idea
in plain Groovy is a list of pairs — the cases become data you can build,
pass around and extend by appending rather than by editing a switch:
def cases = [
[{ int n -> n % 15 == 0 }, '🥤🐝'],
[{ int n -> n % 3 == 0 }, '🥤'],
[{ int n -> n % 5 == 0 }, '🐝'],
]
def fizzbuzz = { int n ->
cases.find { pred, _ -> pred(n) }?.last() ?: n.toString()
}
A query: GINQ
GINQ (Groovy-Integrated
Query) gives FizzBuzz a SQL-shaped spelling with from … select …. It is
gloriously over-engineered for a problem this small, but it is a genuinely
different paradigm — and it would scale to filtering, grouping and joins if the
query grew:
def result = GQ {
from n in (1..20)
select n % 15 == 0 ? '🥤🐝'
: n % 3 == 0 ? '🥤'
: n % 5 == 0 ? '🐝'
: n.toString()
}.toList()
FizzBuzz in parallel
FizzBuzz is embarrassingly parallel: each element depends only on its own
number, so the work splits cleanly. The earlier GPars post ran the parallel
version on the GPars library; in Groovy 6 those patterns
are integrated into the core (GEP-18).
ParallelScope.withPool(Pool.virtual()) binds a virtual-thread pool for the
scope, and collectParallel is the parallel collect:
import groovy.concurrent.Pool
import groovy.concurrent.ParallelScope
def result = ParallelScope.withPool(Pool.virtual()) { scope ->
(1..20).collectParallel { fizzbuzz(it) }
}
This borrows the framing from the Concurrency "lite" section of
groovy-verify: factor a concurrent computation into a structural guarantee
the runtime provides and a local obligation you must get right. Here the
structural half is "an order-preserving parallel map gives the same result as
the sequential one" — collectParallel keeps encounter order — so all that is
left is the per-element mapping. The mapping is unchanged from the sequential
versions, which is the whole point: the fizzbuzz closure drops straight in.
That same mapping drops just as easily into ordinary Java Streams — the form Don Raab uses — where switching to parallel is a one-word change:
import java.util.stream.IntStream
def result = IntStream.rangeClosed(1, 20)
.mapToObj { fizzbuzz(it) } // .parallel() before this to fan out
.toList()
Proving FizzBuzz
If parallel FizzBuzz is "get the per-element mapping right and the structure
takes care of the rest", it is fair to ask: can we prove we got that element
right? The
groovy-verify project — an
SMT-backed @TypeChecked extension that discharges
groovy.contracts annotations at
compile time — does exactly that, in the Dafny style of element-wise array
verification. A pure spec(n) (the nested ternary from earlier), a loop that
fills r[i] = spec(i + 1), and a postcondition saying every slot matches it:
@TypeChecked(extensions = 'verification.VerifyChecker')
static String[] build(int upTo) {
String[] r = new String[upTo]
int i = 0
@Invariant({ 0 <= i && i <= upTo && r.length == upTo &&
(0..<i).every { int k -> r[k] == FizzBuzz.spec(k + 1) } })
@Decreases({ upTo - i })
while (i < upTo) {
r[i] = FizzBuzz.spec(i + 1)
i = i + 1
}
return r
}
FizzBuzz counts from 1, so the 0-based slot i holds the value for number
i + 1 — that + 1 is the bridge between the array index and the FizzBuzz
number. The module compiles only because the solver can prove no slot holds the
wrong emoji. Introduce the classic off-by-one — r[i] = spec(i) — and the loop
invariant is no longer preserved; compilation fails with a concrete
counterexample naming the offending slot:
[Static type checking] - Cannot prove loop invariant is preserved by the loop body in build
counterexample: i = 0, r.length = 1, upTo = 1
r[0] = "🥤🐝" — the spec requires "1"
fails on: build(1)
Slot 0 would hold spec(0) == "🥤🐝" (zero divides everything, so it’s
FizzBuzz) where number 1 requires "1". "Correct" here means faithful to `spec`: the loop provably realises it
at every index, with no gap, overwrite or off-by-one. It doesn’t claim the spec
itself is the One True FizzBuzz.
Parallel, checked and proven
The last two sections each told half a story. The parallel version leaned on a structural guarantee (an order-preserving parallel map) so it only had to get the per-element mapping right; the verified version proved a per-element mapping right. What happens when we put them together — and need to combine the parallel results rather than just collect them?
When the parallel version assembles its per-number tokens, the combine step
is the most natural operation in FizzBuzz: gluing the rendered emoji strings
together. String concatenation is a monoid — associative, with the empty
string as identity — and that associativity is exactly what a parallel
reduction relies on: partition the range, render each chunk, recombine, and the
result must not depend on where the splits fell. Groovy 6 ships
CombinerChecker (from the
functional-programming post)
to check that algebraic shape; groovy-verify proves the semantics.
Turning on both checkers over one class is a genuine two-checker compile:
@TypeChecked(extensions = ['groovy.typecheckers.CombinerChecker', 'verification.VerifyChecker'])
class Join {
@Reducer(zero = '""') // string concat is a monoid; CombinerChecker trusts this & checks the seed
@Ensures({ result == a + b }) // the combiner's defining equation, proven
static String glue(String a, String b) { a + b }
@Ensures({ (a + b) + c == a + (b + c) }) // associativity — proven for ALL strings (the law sumParallel needs)
static void associative(String a, String b, String c) { }
@Ensures({ a + '' == a && '' + a == a }) // identity — '' is a true zero, proven
static void identity(String a) { }
@Ensures({ '🥤' + '🐝' == '🥤🐝' }) // not just abstract strings — the actual emoji, proven
static void emojiGlue() { }
static void parallelGlue() {
(1..20).collect { spec(it) + ' ' }.sumParallel(Join::glue) // certified site
}
}
The two checkers keep their error channels separate but reinforce each other — shape beside semantics:
-
CombinerCheckervalidates thesumParallel(Join::glue)call site:gluecarries@Reducer, so the combiner is declared associative and the seedless parallel reduction is safe. It trusts the annotation. -
VerifyChecker(the Z3 backend) discharges what the annotation claims. Groovy’s+onStringlowers to Z3’s theory of sequences, so the monoid laws prove over arbitrary strings — and the solver reasons about the literal emoji too:'🥤' + '🐝' == '🥤🐝'is a proof, not an assertion.
In fact, those explicit associative and identity methods are more teaching
aid than requirement: groovy-verify derives both laws from @Reducer
itself, so the combiner alone carries the proof —
@Reducer(zero = '""') // associativity + identity proven implicitly, from @Reducer
@Ensures({ result == a + b })
static String glue(String a, String b) { a + b }
The synergy cuts both ways. A non-associative inline combiner like
injectParallel(0) { a, b → a - b } is a CombinerChecker error from static
shape analysis alone. A method falsely annotated @Associative is trusted by
CombinerChecker — but refuted by the verifier, which cannot prove the law
holds. And the proof is genuine, not skipped: concatenation is associative but
not commutative, and claiming otherwise is rejected with a minimal emoji
witness:
[Static type checking] - Cannot prove postcondition of claim holds on this return path
ensured: ((🥤 + a) == (a + 🥤))
fails on: claim("A")
That is, "🥤" + "A" ≠ "A" + "🥤". One checker reasons about the syntactic
shape, the other about the values; the combiner has to satisfy both.
Stand the two verified sections side by side and the parallel FizzBuzz is
correct end to end: every element is proven faithful to spec (the array
build), and the combine step is a proven monoid (this Join), so the parallel
join is guaranteed to equal the sequential one for any split. The runtime
supplies the parallelism; the compiler supplies the proof that using it changes
nothing.
And the proof isn’t tied to Groovy’s parallel collections. The JDK’s own
Stream.reduce(identity, accumulator) documents that, for correct parallel
results, the identity must be a genuine identity and the accumulator must be
associative — exactly the monoid contract we just proved. So the same glue
drops straight into the ordinary-streams form from earlier, fanned out:
tokens.parallelStream().reduce('', Join::glue) // == the sequential join
|
Note
|
Proving the monoid laws is not a detour around the parallel claim — it is the claim: associativity and identity are exactly what make `sumParallel’s partition-and-recombine agree with any sequential fold, so the algebra is the right thing to prove. |
A speculative Groovy 7 version
Don Raab’s Java 26 version finally gets primitive patterns — the payoff of a ten-year wait:
each -> switch (each) {
case int i when i % 15 == 0 -> "🥤🐝";
case int i when i % 3 == 0 -> "🥤";
case int i when i % 5 == 0 -> "🐝";
default -> Integer.toString(each);
};
Groovy didn’t have to wait. Its standard answer is the closure-case switch from the very first example — available since long before that ten-year clock started ticking, and running unchanged on every currently supported JDK:
switch (n) {
case { it % 15 == 0 } -> '🥤🐝'
case { it % 3 == 0 } -> '🥤'
case { it % 5 == 0 } -> '🐝'
default -> n.toString()
}
What does wait on Groovy 7 is the exact match of Java’s spelling — a primitive
type pattern int i that binds and narrows within the case body and its when
guard. GEP-19 (Structural Pattern Matching in
switch) sketches it, aligning Groovy’s surface syntax with Java’s
pattern-matching trajectory:
switch (n) {
case int i when i % 15 == 0 -> '🥤🐝'
case int i when i % 3 == 0 -> '🥤'
case int i when i % 5 == 0 -> '🐝'
default -> n.toString()
}
GEP-19 also brings list patterns — and here is the promised payoff for that
stringified-key switch in the truth-table section. Instead of comparing
[true, true] as text, you destructure the two remainders and match on the
structure directly; the .toString() disappears:
switch ([n % 3, n % 5]) {
case [var a, var b] when a == 0 && b == 0 -> '🥤🐝'
case [0, var _] -> '🥤'
case [var _, 0] -> '🐝'
default -> n
}
Two details follow from GEP-19’s own disambiguation rule: the wildcard is
var (a bare stays an ordinary identifier), and an all-constant label like
[0, 0] keeps its legacy isCase meaning — so the both-zero row earns its
binding-and-guard rather than matching a constant pair directly.
To be clear, both speculative forms are just that — GEP-19 is a draft targeting Groovy 7 and is not implemented today, so neither compiles on Groovy 6; they live in the companion repo (kept out of the build) only to show where the alignment leads. The point is the contrast: the capability has been there for years and on every JDK; GEP-19 brings the matching syntax, not the power.
Metaprogramming, handle with care
One Groovy capability hasn’t appeared yet: runtime metaprogramming — reshaping a type through its metaclass, and overloading operators by name. It’s a real power tool, and the engine behind much of Groovy’s DSL and builder support. FizzBuzz is far too small to need any of it, which makes it a fun way to see how far the language will bend — with the usual caveat that these are tools to reach for deliberately, where they earn their keep.
ExpandoMetaClass can add a property to a type, so every Integer answers for
itself:
Integer.metaClass.getFizzBuzz = { ->
(delegate % 15 == 0) ? '🥤🐝' :
(delegate % 3 == 0) ? '🥤' :
(delegate % 5 == 0) ? '🐝' : delegate
}
(1..20).each { println it.fizzBuzz }
Now 7.fizzBuzz == 7 and 15.fizzBuzz == '🥤🐝' — the numbers answer for
themselves. Tidy, bearing in mind it mutates a JDK type’s metaclass for the whole
runtime, which is the kind of reach worth weighing first.
Operators are methods too, so multiply can take a new argument type. Teach
Integer a multiply(String) and (it % 3) * '🥤' becomes meaningful — it
yields the emoji exactly when the remainder is zero:
Integer.metaClass.multiply = { String s -> delegate == 0 ? s : '' }
(1..20).each { println(((it % 3) * '🥤' + (it % 5) * '🐝') ?: it) }
And if you would rather not metaprogram at all, the same idea sits in plain
arithmetic: '🥤' * n repeats the string n times, so a 0/1 multiplier does the
selecting:
(1..20).each {
println(('🥤' * ((it % 3 + 2) / 3 as int % 2 ^ 1) +
'🐝' * ((it % 5 + 4) / 5 as int % 2 ^ 1)) ?: it)
}
All three live in the companion repo’s diabolical/ module. They are a romp at
the expressive end of Groovy; in real code the metaclass techniques earn their
place in DSLs and frameworks rather than a five-line kata. Powerful when you need
them — reach for them when they pull their weight, and leave them be when a plain
function will do.
Conclusion
None of these is the way to write FizzBuzz, and that was never the goal. The fun is in how many shapes the same three rules can take: a switch of predicates, a nested ternary, a string you build and let collapse, two periodic patterns you index — or lazily zip forever — a 2×2 truth table you index or switch on, a list of cases as data, a SQL-shaped query, the same mapping fanned across virtual threads, a version a solver proves correct before it ever runs, and a few that bend Groovy’s metaprogramming to the task. Pick the one that makes you smile — and add emoji. 😀
Further information
-
Companion code for this post: https://github.com/paulk-asert/groovy-fizzbuzz
-
Don Raab’s Ternary, Predicate, and Pattern Matching for FizzBuzz with Java 26 — the source of the emoji idea
-
The
groovy-verifyFizzBuzz example and its Concurrency "lite" section -
GEP-19 — Structural Pattern Matching in switch (Groovy 7 candidate)
