Ah, the classic FizzBuzz coding challenge.
I’ve decided to include this picture to get your attention. Oh, and also because it associates with the “Buzz” part of the challenge.
“Buzz” – alarm clock…
Onward! Do you know that you don’t have even a single excuse to fail at one of these? So you think that you know that coding kata well?
Let your imagination run wild:
You are at the coding interview, and the interviewer has just asked you to implement FizzBuzz and given you the following problem description:
Write a program that prints a sequence of numbers from 1 to N.
Every number that divides by the three should appear as “Fizz.”
Every number that divides by the five should appear as “Buzz.”
Every number that divides by the three and the five should appear as “FizzBuzz.”
– anonymous interviewer
A piece of cake!
So you are writing away, and the code is pouring out of you…
Five or ten minutes in and you are done. You feel the interviewer might just suspect that you were ready for the challenge. In that case, they will have some additional unexpected requirements for you.
Just like in the real world programming.
Continue reading, and we will explore these additional requirements and possible solution for them. And for now, we will solve the FizzBuzz challenge in Kotlin.
It is probably a good idea to have some automated tests so that we know when we are done. Let’s create a new test suite in JUnit4:
import org.junit.Assert.*
import org.junit.Test
@Suppress("FunctionName")
class FizzBuzzTest {
}
Now we are going to write a first test – the simplest one, that covers the fact that we can produce a normal number sequence without any “fizzes” or “buzzes”:
@Test
fun `simple numbers`() {
// ARRANGE
val fizzBuzz = FizzBuzz()
// ACT
val sequence = fizzBuzz.generateSequence(size = 2)
// ASSERT
assertEquals(listOf("1", "2"), sequence)
}
I like to split my tests in three sections: arrange, act and assert. It is not necessary to write out loud these comments as I do here. I don’t usually do myself so, but I think about these three sections when constructing a good test.
On the other hand, I highly recommend you DO split your tests in such three sections. And write down these comments too for the sake of explicitness.
If you are already comfortable writing great tests, feel free to ignore my advice.
Onward! There are few compile errors in this test that we will need to deal with. First, we haven’t defined FizzBuzz
class yet. Second, we will need to create the generateSequence
method.
Just to make things compile, we will make that method return an empty list:
class FizzBuzz {
fun generateSequence(size: Int): List<String> {
return emptyList()
}
}
Now we can run our test. As expected, it fails because we are expecting a list with two items ["1", "2"]
, but we are getting an empty list []
.
Making this pass is pretty simple. We are going to generate a sequence from one to size
using Kotlin’s range syntax:
1..size
Then we are going to generate a new sequence out of it by calling toString()
method on every item of that sequence. To do that we are going to employ map
function defined on any Kotlin sequence:
fun generateSequence(size: Int): List<String> {
return (1..size).map { it.toString() }
}
Oh, by the way.
Wondering what all these neat features of Kotlin are? Maybe you want to learn how to build a full-fledged application in Kotlin?
I have accidentally written this 80-page hands-on getting started tutorial for Kotlin. Feel welcome to download the free PDF Ultimate Tutorial: Getting Started With Kotlin. You are going to learn how to build a full-fledged command-line application in Kotlin. You’ll get free access preview for Web app and Android app tutorials, as well!
Anyways, we should run our tests now.
And they all pass.
Alright. Our next step is to start handling some “fizz” numbers. For that, we can write a new test. It is as simple as copy-pasting (oh no!) the previous test and making some modifications to it:
@Test
fun `fizz numbers`() {
// ARRANGE
val fizzBuzz = FizzBuzz()
// ACT
val sequence = fizzBuzz.generateSequence(size = 4)
// ASSERT
assertEquals(listOf("1", "2", "Fizz", "4"), sequence)
}
Now we are going from one to four, and we are expecting all numbers to be normal except for number three. It should become “Fizz” in the generated sequence.
If we run this test, it is going to fail, because so far we are producing only simple numbers converted to strings.
To make this test pass we are going to extract it.toString()
part into a private method generateMethod(number: Int): String
. We will add an “if” statement to handle the case when the number divides by three:
fun generateSequence(size: Int): List<String> {
return (1..size).map { generateNumber(it) }
}
private fun generateNumber(number: Int): String {
if (number % 3 == 0) {
return "Fizz"
}
return number.toString()
}
It passes the test suite now.
Next, we would like to write a test for the “Buzz” requirement. For that, we are going to generate a sequence from one to fourteen. There will be a few “fizzes” and “buzzes” there.
No “fizzbuzzes” yet, though.
@Test
fun `buzz numbers`() {
// ARRANGE
val fizzBuzz = FizzBuzz()
// ACT
val sequence = fizzBuzz.generateSequence(size = 14)
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "13", "14"), sequence)
}
That test fails because we are not producing any “buzzes” yet. We will add another “if” statement to handle that case:
private fun generateNumber(number: Int): String {
if (number % 3 == 0) {
return "Fizz"
}
if (number % 5 == 0) {
return "Buzz"
}
return number.toString()
}
And the test is passing.
Finally, we want to write a test involving some “fizzbuzzy” numbers. In this case, we want to generate a sequence from one to thirty-one, so that we include at least two of these:
@Test
fun `fizz buzz numbers`() {
// ARRANGE
val fizzBuzz = FizzBuzz()
// ACT
val sequence = fizzBuzz.generateSequence(size = 31)
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "13", "14", "FizzBuzz",
"16", "17", "Fizz", "19", "Buzz",
"Fizz", "22", "23", "Fizz", "Buzz",
"26", "Fizz", "28", "29", "FizzBuzz",
"31"), sequence)
}
And of course it does not work.
That is because the first “if” statement (for “fizzes”) catches this case and produces “Fizz.” To fix that we will have to write a new “if” statement that handles the case for “fizzes” and “buzzes” at the same time.
That case will have to be checked before all others. That way, nor “Fizz,” nor “Buzz,” will match before it:
private fun generateNumber(number: Int): String {
if (number % 3 == 0 && number % 5 == 0) {
return "FizzBuzz"
}
if (number % 3 == 0) {
return "Fizz"
}
if (number % 5 == 0) {
return "Buzz"
}
return number.toString()
}
Alrighty! All tests are passing now.
Let’s extract some duplication in the test suite. Notably, the ARRANGE
part. That is because it is the same for every single test. We will promote fizzBuzz
local variable to the private field of the test suite class:
import org.junit.Assert.*
import org.junit.Test
@Suppress("FunctionName")
class FizzBuzzTest {
private val fizzBuzz = FizzBuzz()
@Test
fun `simple numbers`() {
// ACT
val sequence = fizzBuzz.generateSequence(size = 2)
// ASSERT
assertEquals(listOf("1", "2"), sequence)
}
@Test
fun `fizz numbers`() {
// ACT
val sequence = fizzBuzz.generateSequence(size = 4)
// ASSERT
assertEquals(listOf("1", "2", "Fizz", "4"), sequence)
}
@Test
fun `buzz numbers`() {
// ACT
val sequence = fizzBuzz.generateSequence(size = 14)
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "13", "14"), sequence)
}
@Test
fun `fizz buzz numbers`() {
// ACT
val sequence = fizzBuzz.generateSequence(size = 31)
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "13", "14", "FizzBuzz",
"16", "17", "Fizz", "19", "Buzz",
"Fizz", "22", "23", "Fizz", "Buzz",
"26", "Fizz", "28", "29", "FizzBuzz",
"31"), sequence)
}
}
If we run the tests, they should still pass. As our next step, we should take a careful look if there is any duplication in the production code.
And there is some!
The checks for “fizziness” and “buzziness” are duplicated between “fizzbuzz” case handler and corresponding “fizz” or “buzz” case handler. Simplest refactoring we can do is to extract these into private functions:
private fun generateNumber(number: Int): String {
if (isFizz(number) && isBuzz(number)) {
return "FizzBuzz"
}
if (isFizz(number)) {
return "Fizz"
}
if (isBuzz(number)) {
return "Buzz"
}
return number.toString()
}
private fun isBuzz(number: Int) = number % 5 == 0
private fun isFizz(number: Int) = number % 3 == 0
At that point, you were about to refactor some more, for example, extract magic strings to constants.
But the interviewer told you it is enough refactoring.
You see they are impressed. You have finished earlier after all. But you feel as if the interviewer is a little bit suspicious. It is a classic exercise after all.
So they decide to give you an additional requirement!
A number should also be “Fizz” if it contains digit “3.”
A number should also be “Buzz” if it contains digit “5.”
A number should also be “FizzBuzz” if it contains both.
– anonymous interviewer
Oh, no!
Nobody told you there could be more…
Don’t fret! We are going to go through that. Let’s add a new test for the “Fizz” part with the new requirement.
We are going to generate the sequence until 32. Some numbers, which weren’t “Fizz” before, will become such:
@Test
fun `fizz numbers that just contain digit '3'`() {
// ACT
val sequence = fizzBuzz.generateSequence(size = 32)
// 13 becomes Fizz
// 23 becomes Fizz
// 31 becomes Fizz
// 32 becomes Fizz
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "Fizz", "14", "FizzBuzz",
"16", "17", "Fizz", "19", "Buzz",
"Fizz", "22", "Fizz", "Fizz", "Buzz",
"26", "Fizz", "28", "29", "FizzBuzz",
"Fizz", "Fizz"), sequence)
}
Of course, the test is failing.
To make it pass we will need to modify the function isFizz
appropriately. You should include the check for “it contains digit 3” on top of the original condition. In boolean logic (last time I checked) it was an “OR” operation between those two conditions:
private fun isFizz(number: Int) =
number % 3 == 0 || number.toString().contains('3')
Note the number.toString().contains('3')
part. The test is passing now. But a few of other tests are failing!
Oh no!
We will have to fix those tests. We should replace numbers containing digit “3” with “Fizz”:
@Test
fun `buzz numbers`() {
// ACT
val sequence = fizzBuzz.generateSequence(size = 14)
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "Fizz", "14"), sequence)
}
@Test
fun `fizz buzz numbers`() {
// ACT
val sequence = fizzBuzz.generateSequence(size = 31)
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "Fizz", "14", "FizzBuzz",
"16", "17", "Fizz", "19", "Buzz",
"Fizz", "22", "Fizz", "Fizz", "Buzz",
"26", "Fizz", "28", "29", "FizzBuzz",
"Fizz"), sequence)
}
Hold on a second… Doesn’t this feel weird to you?
Yeah, I think this last case covers all our edge cases. That is because it (kind of) includes all other tests. So you should get rid of those tests!
Oh, and don’t forget to demote the private field fizzBuzz
back to the local variable.
Here we go:
import org.junit.Assert.*
import org.junit.Test
@Suppress("FunctionName")
class FizzBuzzTest {
@Test
fun `generating fizz buzz sequence`() {
// ARRANGE
val fizzBuzz = FizzBuzz()
// ACT
val sequence = fizzBuzz.generateSequence(size = 32)
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "Fizz", "14", "FizzBuzz",
"16", "17", "Fizz", "19", "Buzz",
"Fizz", "22", "Fizz", "Fizz", "Buzz",
"26", "Fizz", "28", "29", "FizzBuzz",
"Fizz", "Fizz"), sequence)
}
}
Now we want to implement final two requirements.
For that let’s change our test to go until 53
, so we hit more attractive edge cases. And some of the numbers will become “Buzz” and some other numbers ought to become “FizzBuzz” now.
See for yourself:
@Test
fun `generating fizz buzz sequence`() {
// ARRANGE
val fizzBuzz = FizzBuzz()
// ACT
val sequence = fizzBuzz.generateSequence(size = 53)
// 35 became FizzBuzz
// 51 became FizzBuzz
// 52 became Buzz
// 53 became FizzBuzz
// ASSERT
assertEquals(listOf(
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "Fizz", "14", "FizzBuzz",
"16", "17", "Fizz", "19", "Buzz",
"Fizz", "22", "Fizz", "Fizz", "Buzz",
"26", "Fizz", "28", "29", "FizzBuzz",
"Fizz", "Fizz", "Fizz", "Fizz", "FizzBuzz",
"Fizz", "Fizz", "Fizz", "Fizz", "Buzz",
"41", "Fizz", "Fizz", "44", "FizzBuzz",
"46", "47", "Fizz", "49", "Buzz",
"FizzBuzz", "Buzz", "FizzBuzz"), sequence)
}
To make this pass, we will need to add similar condition to the isBuzz
private function. Notice the number.toString().contains('5')
part.
private fun isBuzz(number: Int) =
number % 5 == 0 || number.toString().contains('5')
The tests are back to green now (they pass).
Don’t you notice how isBuzz
and isFizz
functions have a similar structure? Don’t you think we should be able to extract that structure?
Let’s do it!
First, we will extract dividesBy(number, divider)
private function:
private fun isBuzz(number: Int) =
dividesBy(number, 5) || number.toString().contains('5')
private fun isFizz(number: Int) =
dividesBy(number, 3) || number.toString().contains('3')
private fun dividesBy(number: Int, divider: Int) =
number % divider == 0
Second, we will extract containsDigit(number, digit)
private function.
private fun isBuzz(number: Int) =
dividesBy(number, 5) || containsDigit(number, 5)
private fun isFizz(number: Int) =
dividesBy(number, 3) || containsDigit(number, 3)
private fun containsDigit(number: Int, digit: Int) =
number.toString().contains(digit.toString())
Note how digit
here is not a character or string. It is an integer.
That way, we should be able to extract the whole structure (dividesBy
or containsDigit
) out of those two functions.
Here we go:
private fun isBuzz(number: Int) =
dividesOrContainsDigit(number, 5)
private fun isFizz(number: Int) =
dividesOrContainsDigit(number, 3)
private fun dividesOrContainsDigit(number: Int, digit: Int) =
dividesBy(number, digit) || containsDigit(number, digit)
Your interviewer is not stopping your refactoring yet. So maybe he is out of requirements?
Thank goodness!
Finally, you can do that magic strings extraction refactoring! For that, we will extract “Fizz” and “Buzz” strings out of respective edge cases. And from the “fizzbuzzy” one too:
companion object {
private val FIZZ = "Fizz"
private val BUZZ = "Buzz"
private val FIZZ_BUZZ = "$FIZZ$BUZZ"
}
private fun generateNumber(number: Int): String {
if (isFizz(number) && isBuzz(number)) {
return FIZZ_BUZZ
}
if (isFizz(number)) {
return FIZZ
}
if (isBuzz(number)) {
return BUZZ
}
return number.toString()
}
So why are we extracting these magic strings, anyway?
You might ask that yourself…
Or is it just the interviewer asking you that question?
Probably, because you want to make it easy to change one of these strings in just one place, don’t you?
And that is where the thought strikes you! What if I need to change “Fizz” to “Fizzy”?
Yep.
I’ll have to fix all these occurrences in the test suite.
Annoying!
Let’s replace all the magic strings in the test too. We will have to promote these private constants FIZZ
, BUZZ
, and FIZZ_BUZZ
to internal access level so that that the test can access them:
companion object {
internal val FIZZ = "Fizz"
internal val BUZZ = "Buzz"
internal val FIZZ_BUZZ = "$FIZZ$BUZZ"
}
And finally, we are going to replace the string occurrences in the test suite:
import FizzBuzz.Companion.BUZZ
import FizzBuzz.Companion.FIZZ
import FizzBuzz.Companion.FIZZ_BUZZ
import org.junit.Assert.*
import org.junit.Test
@Suppress("FunctionName")
class FizzBuzzTest {
@Test
fun `generating fizz buzz sequence`() {
// ARRANGE
val fizzBuzz = FizzBuzz()
// ACT
val sequence = fizzBuzz.generateSequence(size = 53)
// ASSERT
assertEquals(listOf(
"1", "2", FIZZ, "4", BUZZ,
FIZZ, "7", "8", FIZZ, BUZZ,
"11", FIZZ, FIZZ, "14", FIZZ_BUZZ,
"16", "17", FIZZ, "19", BUZZ,
FIZZ, "22", FIZZ, FIZZ, BUZZ,
"26", FIZZ, "28", "29", FIZZ_BUZZ,
FIZZ, FIZZ, FIZZ, FIZZ, FIZZ_BUZZ,
FIZZ, FIZZ, FIZZ, FIZZ, BUZZ,
"41", FIZZ, FIZZ, "44", FIZZ_BUZZ,
"46", "47", FIZZ, "49", BUZZ,
FIZZ_BUZZ, BUZZ, FIZZ_BUZZ), sequence)
}
}
All your tests should be green at this point.
Did you need to do all these refactorings?
Not necessarily.
Do you need to know how to do them, so that you are ready for any requirement change?
Absolutely!
Here is the final production code:
class FizzBuzz {
companion object {
internal val FIZZ = "Fizz"
internal val BUZZ = "Buzz"
internal val FIZZ_BUZZ = "$FIZZ$BUZZ"
}
fun generateSequence(size: Int): List<String> {
return (1..size).map { generateNumber(it) }
}
private fun generateNumber(number: Int): String {
if (isFizz(number) && isBuzz(number)) {
return FIZZ_BUZZ
}
if (isFizz(number)) {
return FIZZ
}
if (isBuzz(number)) {
return BUZZ
}
return number.toString()
}
private fun isBuzz(number: Int) =
dividesOrContainsDigit(number, 5)
private fun isFizz(number: Int) =
dividesOrContainsDigit(number, 3)
private fun dividesOrContainsDigit(number: Int, digit: Int) =
dividesBy(number, digit) || containsDigit(number, digit)
private fun containsDigit(number: Int, digit: Int) =
number.toString().contains(digit.toString())
private fun dividesBy(number: Int, divider: Int) =
number % divider == 0
}
I genuinely thank you for reading, and I encourage you to practice your basic foundations.
There are tons of different challenges for that on websites like http://codingdojo.org/kata/ and http://kata-log.rocks. Huge thank you to creators and maintainers!
Liked this article? – Share it with your friends, on social media, hacker news, Reddit, etc. If you are interested in Kotlin, check out more of my stuff on Kotlin.
Top comments (9)
This is scarily close to being overengineered, IMHO
I guess that is why I add at the end:
I would probably not do the Strings extraction here. At least, not until there is a feature on the top of backlog that requires it.
With the method extraction, I would probably leave it at the first level methods extraction (
isFizz
andisBuzz
, without others). Again, until there is a feature that will make these other private methods handy.As with any practice code, you want to take things to the max and extreme. You do that to see how things could be. And think when you would and would not do certain things (like I did in previous 2 paragraphs here).
Essentially, doing that expands your arsenal, when practicing and learning.
I can very easily write a fizzbuzz implementation that pass all your tests but is completly wrong. For example:
So I would throw in properties based testing, here with kotlin test
Yes. You can.
But will you, really?
IMHO alert.
In Test-Driven Development, you pass the failing test with the simplest possible solution. And your solution has a higher complexity than the one proposed in the article.
You have one edge case handler more (additional “if” statement).
Therefore, if TDD practiced properly, such “evil” solution will not be written.
Anyways.
I agree that PBT has a higher degree of correctness when it comes to testing. Don’t forget, though, that EBT is easier to read and reason about.
I mean, look at that:
There is just so much stuff involved here. Show that to an apprentice-level developer and they will have a lot of questions about this test. It is not simple.
For this "Multiples of 15" test, my refactored version would be something like
As you can see one small pb was that I was too lazy to lookup how to test for only positive numbers. The harder and much more interesting one is I'm not used to think about integer overflows. This style of testing forced me to think about it because my test kept failing! That was actually a teachable lesson.
Anyway I highly recommend this, most fun presentation about testing I have ever read!
slideshare.net/ScottWlaschin/an-in...
Great refactoring!
Still the "15 * nb" part requires a mental leap.
Example tests are nice because, when written well, they can be read very quickly (like prose).
Another thing. So what if I don't need my code to handle overflows? Because all the numbers that business wants to work with are under 500.
YAGNI in that case.
I've taken a look at the presentation. And I'm still convinced that 2 tests is enough to drive out correct "add" code if you follow 3 rules of TDD.
And if you doubt that I thought about this enough, here is my own presentation about PBT: github.com/waterlink/property-base...
Another way to look at this “simplest possible way to make the failing test pass” is to imagine you have a developer on the team, who is removing any code that is not tested.
So that additional if statement is not tested by the test. That means it can be safely removed.
You can apply such radical technique to teams that do TDD rigorously. And if you knew there is a person on your team (and that person – is every member of the team), you are probably going to make sure to have all the production code covered.
You just don’t add any code that is not tested, as simple as that.
Simple but a very good teachable tdd article.
Look forward to your IOS version, Kotlin series of learning articles.