FizzBuzz For Interviews

by Richard Taylor : 2023-01-11

Introduction

For several years at UKCloud we used the infamous FizzBuzz game as a coding "test" in interviews. It was always stressed that this was a starting point for a "conversation about coding" and not an exam. We neither required or wanted candidates to code up a perfect solution in silence and then say "OK, done".

To some extent this was born out of the interviewers' own experience of coding tests where we had sat in silence thinking "I should be able to do this... but my mind has gone blank".

FizzBuzz is simple enough that everyone should be able to get a solution working with some coaching. It's also just about complex enough to introduce some more advanced concepts, particularly around testing.

Now that UKCloud has gone into liquidation I will be back on the job market for the first time in 7 years: so it seems like a good time to reflect on the ins and outs of this kind of challenge.

The Challenge

Here's a reminder of the challenge statement:

/**
 * Play the fizz buzz game.
 *
 * For all the numbers from 1 to 100, either
 *
 * Print the number
 * Print "Fizz" if the number is a multiple of 3
 * Print "Buzz" if the number is a multiple of 5
 * Print "FizzBuzz" if the number is a multiple of 3 and 5.
 */

We do this exercise in whatever language the candidate is most comfortable, but for consistency I will just show Java solutions in this article.

There is a trivial solution:

System.out.println(
"1\n" +
"2\n" +
"Fizz\n" +
"4\n" +
"Buzz\n" +
"Fizz\n" +
"7\n" +
"8\n" +
"Fizz\n" +
"Buzz\n" +
"11\n" +
"Fizz\n" +
"13\n" +
"14\n" +
"FizzBuzz\n" +
...

Which I've seen someone start, but not finish. I think they said, correctly, something like "I could go on, but it would be hard to test".

The Basic Solution

Most people come up with something like this pretty quickly. Especially if they have seen the problem before... which most people have, even if they wont admit it.

public class FizzBuzz {

    public void play() {
        for (int number = 1; number <= 100; number++) {

            if (number % 3 == 0 && number % 5 == 0) {
                System.out.println("FizzBuzz");
            }
            else if (number % 3 == 0) {
                System.out.println("Fizz");
            }
            else if (number % 5 == 0) {
                System.out.println("Buzz");
            }
            else {
                System.out.println(number);
            }
        }
    }
}

It's very common for people, through nerves or inexperience, to put the FizzBuzz test at the end so that it never fires, or to forget the "none of the above so just print the number" case.

If that happens you get to see how they handle the results not coming out as expected. Do they notice? How do they react when it's pointed out? You get a small insight into how they go about fixing broken code.

Once something vaguely correct emerges people tend to settle down and we can get on and talk about real development processes. That's why I quite like this challenge: there's a quick win which makes everyone happy.

Is It Right?

Having achieved a working solution the obvious question then is "how do we know this is right for all the numbers?" and then we talk about testing.

Most people know that they should refactor the code into a testable form. How they do that shows a lot about how they think about code and what testing means.

Usually more junior developers will immediately refactor the contents of the loop into a method and write unit tests for that method.

public class FizzBuzz {

    public void play() {
        for (int number = 1; number <= 100; number++) {

            System.out.println(numberToString(number));
        }
    }

    public String numberToString(int number) {
        if (number % 3 == 0 && number % 5 == 0) {
            return "FizzBuzz";
        }
        else if (number % 3 == 0) {
            return "Fizz";
        }
        else if (number % 5 == 0) {
            return "Buzz";
        }
        else {
            return String.valueOf(number);
        }
    }
}

You can chat about which numbers should be tested to give confidence in the code. Which is great until someone asks "Is that testing the play() method?"

It is, indirectly, but you could break that method and all your tests would still pass...

public class FizzBuzz {

    public void play() {
        for (int number = 1; number <= 10; number++) {

            System.out.println(numberToString(number));
        }
    }

    public String numberToString(int number) {
        if (number % 3 == 0 && number % 5 == 0) {
            return "FizzBuzz";
        }
        else if (number % 3 == 0) {
            return "Fizz";
        }
        else if (number % 5 == 0) {
            return "Buzz";
        }
        else {
            return String.valueOf(number);
        }
    }
}

It's only a more experienced developer who will think about injecting a dependency into the object-under-test in order to test the play() method directly.

public class FizzBuzz {

    private final Printer printer;

    public FizzBuzz(Printer printer) {
        this.printer = printer;
    }

    public void play() {
        for (int i = 1; i <= 100; i++) {
            printer.print(numberToString(i));
        }
    }

    ...
}

Here you can check that exactly 100 things are printed. Then you can talk about Inversion of Control and some of the other SOLID principles if you like.

Beyond Basic

Sometimes people come up with different ways to do the basic number-to-string transformation, or the conversation drifts into alternatives for some reason.

Occasionally a candidate will go super terse, as if they think we will be deducting marks for using "extra" characters. But many people will adopt some aspect of the space-saver.

public String numberToString(int number) {
    if(number%15==0)return"FizzBuzz";
    if(number%3==0)return"Fizz";
    if(number%5==0)return"Buzz";
    return""+number;
}

Interestingly, it is rare for people to realise that (n % 15 == 0) is equivalent but more efficient than (n % 3 == 0) && (n % 5 == 0). But sometimes people will say "I could use 15 here, but using 3 and 5 is easier to understand". Which is valid and usually leads to a conversation about comments plus complex code versus plain and simple code.

In an interview situation it is more common for people to go verbose to show that they know about how to write maintainable code.

public String numberToString(int number) {
    String THREE = "Fizz";
    String FIVE = "Buzz";
    String BOTH = "FizzBuzz";

    boolean is3 = isMultiple(number, 3);
    boolean is5 = isMultiple(number, 5);

    if (is3 && is5)
        return BOTH;
    if (is3)
        return THREE;
    if (is5)
        return FIVE;
    return String.valueOf(number);
}

private boolean isMultiple(int number, int factor) {
    return number % factor == 0;
}

One thing that often floors candidates is if you ask how their solution could be extended to include printing "Bang" for multiples of 7. If they start trying to nest "if" statements then you often get into a mess quite quickly. But not if they went for an incremental solution in the first place like this.

public String numberToString(int number) {
    StringBuilder stringBuilder = new StringBuilder();

    if (number % 3 == 0) {
        stringBuilder.append("Fizz");
    }
    if (number % 5 == 0) {
        stringBuilder.append("Buzz");
    }
    if (stringBuilder.length() == 0) {
        stringBuilder.append(number);
    }
    return stringBuilder.toString(); 
}

Here you just have to add 3 lines in the right place and the job's done.

public String numberToString(int number) {

    ...

    if (number % 7 == 0) {
        stringBuilder.append("Bang");
    }

    ...
}

Candidates who have done FizzBuzz before will often produce this solution and say that they chose this way so it can be extended to more numbers. I think it also shows that the candidate has spotted that "FizzBuzz" is not just a random third string in the question, but is "Fizz" (the 3-ness) followed by "Buzz" (the 5-ness), which then naturally extends to any number of prime factors.

(I am a bit biased towards this as a good solution since it's the one I went for when I joined UKCloud, or Skyscape as it was, in 2016. Except I did it in Python... or was it JavaScript?)

Brave Or Foolish?

There are some more exotic solutions that we have discussed as interviewers but never seen in the wild. I guess it's high risk to go with one of these since candidates don't want to come across as a smart arse... especially if they also don't get it quite right.

One that I always thought we might see is to use the fact that the "number number Fizz number Buzz ..." sequence is only 15 items long and then repeats, so you could store that and pick from the list with only one modulus operation and no ifs.

private static final String FIZZ = "Fizz";
private static final String BUZZ = "Buzz";
private static final String N = null;

private static final String[] cache15 = { 
    N, N, FIZZ, N, BUZZ, FIZZ, N, N, FIZZ, BUZZ, 
    N, FIZZ, N, N, FIZZ+BUZZ
};

public String numberToString(int number) {
    String result = cache15[(number - 1) % 15];
    return result != N ? result : String.valueOf(number);
}

OK, the conditional in the return statement is a kind of if.

If someone did come up with this then you could obviously deflate them immediately by asking how they would extend this to 7=Bang but that would be mean, I think. Would I be brave enough to do this in an interview? Probably not... but I might mention that it was an option.

The "craziest" solution I have encountered online is to use Euler's Totient Theorem, like this.

public String numberToString(int number) {

    // Euler's Totient Theorem can be used to show 
    // that (number ^ 4) % 15 must be in {0,1,6,10}

    switch ((number * number * number * number) % 15) {
        case 1: return String.valueOf(number);
        case 6: return "Fizz";
        case 10: return "Buzz";
    }
    return "FizzBuzz";
}

If you go with that then be prepared to explain what a totient is and make sure you put a comment in the code. Depending on how impressed the interviewer is you may immediately get asked how big number has to get before (number ^ 4) causes an overflow... so be ready for that.

Performance

Most of the code we write is network bound so compute performance is not generally something we worry about. It's interesting that when asked "How can we improve this code?" a common answer is "make it faster".

Which leads to a discussion about performance versus maintainability and so on. Amusingly, since FizzBuzz doesn't do very much, it's common for people to say "make it faster" and then not be able to identify any ways to do that.

One way that I touched on above is to replace the pair of tests for divisibility by 3 and divisibility by 5 by a single test of divisibility by 15. But there is a more subtle change that also has a performance gain and raises a more general point. Nearly everyone writes the 3 before the 5, because that's how the challenge reads.

if (number % 3 == 0 && number % 5 == 0) {
    return "FizzBuzz";
}

But if you think about it, and know that && does not evaluate the second test if the first is false, then it's better to write the 5 first, since there are fewer multiples of 5.

if (number % 5 == 0 && number % 3 == 0) {
    return "FizzBuzz";
}

The second way does 120 tests for numbers 1-100 versus 133 tests the other way. It's not a massive difference here, but if there was a big difference in evaluation time and likelihood for some real bit of code then you would definitely want to get it the right way round!

Summary

An interview environment is very different from the normal day-to-day working environment that developers write code in. So you shouldn't extrapolate from a candidate's performance in a straight coding test to judge how they will perform as an employee.

Instead the onus is on you the interviewer to evaluate candidates fairly in a collaborative way, working much as you would when doing your day job.

One way to do that is using a simple challenge like "FizzBuzz" where you can start with a basic solution and use it as a framework for a series of conversations about the craft of software development and engineering.

When UKCloud went into liquidation I forked the public repo where we kept the training examples to keep them for posterity.