FizzBuzz with Groovy and Emojis

Author:  Paul King
PMC Member

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

n % 3 == 0

Fizz

🥤

n % 5 == 0

Buzz

🐝

n % 15 == 0

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:

  • CombinerChecker validates the sumParallel(Join::glue) call site: glue carries @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 + on String lowers 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. 😀