Let's get started with a Microservice Architecture with Spring Cloud:
Implement the FizzBuzz Puzzle in Java
Last updated: January 24, 2026
1. Introduction
In this tutorial, we’ll explore multiple approaches to solving the FizzBuzz programming puzzle in Java.
2. Problem Statement
FizzBuzz is a classic programming problem used to teach division to school children. However, in 2007, Imran Ghory popularized it as a coding interview question. Thereafter, the programming community consistently uses the FizzBuzz problem to test the fundamental concepts, such as conditional logic, modular arithmetic, and code organization.
The problem statement is simple. We are given an integer n, and we print text as we iterate from 1 to n with the following rules:
- For multiples of only 3, we print “Fizz.”
- For multiples of only 5, we print “Buzz.”
- For multiples of both 3 and 5, we print “FizzBuzz.”
- Else we print the number.
As an example, given n = 15, the output should be:
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz.
3. Naive Approach
The default solution is to use the modulo operator (%) to check for divisibility:
public List<String> fizzBuzzNaive(int n) {
List<String> result = new ArrayList<>();
for (int i = 1; i <= n; i++) {
if (i % 3 == 0 && i % 5 == 0) {
result.add("FizzBuzz");
} else if (i % 3 == 0) {
result.add("Fizz");
} else if (i % 5 == 0) {
result.add("Buzz");
} else {
result.add(String.valueOf(i));
}
}
return result;
}
The order of conditions matters. We must check for divisibility by both 3 and 5 first; otherwise, depending on the order of the individual checks, a number like 15 would incorrectly output “Fizz” or “Buzz” instead of “FizzBuzz“.
4. Concatenation Approach
Here, we use StringBuilder from Java to perform string concatenation, thereby avoiding the explicit check for divisibility by 15. To avoid the overhead of repeatedly instantiating a new StringBuilder in every iteration, we reuse a single instance and clear it using setLength(0):
public List<String> fizzBuzzConcatenation(int n) {
List<String> result = new ArrayList<>();
StringBuilder output = new StringBuilder();
for (int i = 1; i <= n; i++) {
if (i % 3 == 0) {
output.append("Fizz");
}
if (i % 5 == 0) {
output.append("Buzz");
}
result.add(output.length() > 0 ? output.toString() : String.valueOf(i));
output.setLength(0);
}
return result;
}
This approach handles the “FizzBuzz” case by default. How? When a number is divisible by both 3 and 5, we trigger both conditions. As a result, we append “Fizz” followed by “Buzz“.
This code is more maintainable when new conditions are added. To add a new condition, we only need to write an if statement. Thus, this solution is more readable compared to the naive method, which has multiple if-then-else blocks. However, having many conditions, each in its own if-block, will also make the code difficult to read.
5. Counter-Based Approach
The modulo operation is computationally expensive for large values of n. We can eliminate it using counters. As before, we reuse a single StringBuilder instance:
public List<String> fizzBuzzCounter(int n) {
List<String> result = new ArrayList<>();
StringBuilder output = new StringBuilder();
int fizz = 0;
int buzz = 0;
for (int i = 1; i <= n; i++) {
fizz++;
buzz++;
if (fizz == 3) {
output.append("Fizz");
fizz = 0;
}
if (buzz == 5) {
output.append("Buzz");
buzz = 0;
}
result.add(output.length() > 0 ? output.toString() : String.valueOf(i));
output.setLength(0);
}
return result;
}
In this approach, we use two counters to track the delta from the last multiple of 3 and the last multiple of 5. When a counter reaches its target value, we append the corresponding word to the StringBuilder and reset the counter to zero. This method also takes O(n) time and O(n) space.
6. Testing
To begin with, we create the FizzBuzzUnitTest class:
class FizzBuzzUnitTest {
private FizzBuzz fizzBuzz;
private static final List GROUND_TRUTH_SEQUENCE_LENGTH_5 = generateGroundTruth(5);
private static final List GROUND_TRUTH_SEQUENCE_LENGTH_100 = generateGroundTruth(100);
@BeforeEach
void setUp() {
fizzBuzz = new FizzBuzz();
}
private static List generateGroundTruth(int n) {
return IntStream.rangeClosed(1, n)
.mapToObj(i -> {
if (i % 15 == 0) return "FizzBuzz";
if (i % 3 == 0) return "Fizz";
if (i % 5 == 0) return "Buzz";
return String.valueOf(i);
})
.collect(Collectors.toList());
}
}
Here, we set the ground truth output for n = 5 in the variable GROUND_TRUTH_SEQUENCE_LENGTH_5, and for n = 100 in the variable GROUND_TRUTH_SEQUENCE_LENGTH_100.
We first test all three approaches for the case n < 15 by taking n = 5:
@Test
void givenSequenceLength5_whenAllMethods_thenReturnCorrectSequence() {
List naiveResult = fizzBuzz.fizzBuzzNaive(5);
List concatResult = fizzBuzz.fizzBuzzConcatenation(5);
List counterResult = fizzBuzz.fizzBuzzCounter(5);
assertAll(
() -> assertEquals(GROUND_TRUTH_SEQUENCE_LENGTH_5, naiveResult,
"fizzBuzzNaive should return correct sequence for n=5"),
() -> assertEquals(GROUND_TRUTH_SEQUENCE_LENGTH_5, concatResult,
"fizzBuzzConcatenation should return correct sequence for n=5"),
() -> assertEquals(GROUND_TRUTH_SEQUENCE_LENGTH_5, counterResult,
"fizzBuzzCounter should return correct sequence for n=5")
);
}
The other test (for n = 100) is analogous.
7. Conclusion
In this article, we covered multiple approaches to solving the FizzBuzz problem in Java. We began with the naive modulo-based approach, then transitioned to string concatenation for simplicity and readability, and finally covered the optimized counter-based solution that eliminates modulo operations. The concatenation approach is more readable, but the counter-based approach performed best for larger values of n (>100).
As always, the complete source code is available over on GitHub.















