FizzBuzz? Really?

Yes, it's FizzBuzz, but without the modulo! Exploring FizzBuzz as an interview exercise, and implemented like the children's counting game that inspired it.

FizzBuzz? Really?

Okay, hear me out. For one thing, everyone else has written a FizzBuzz article already and I don't want to be left out. For another, I think this is an interesting take on it. For three, maybe I can just show the interviewer this post the next time I'm interviewing? Maybe. I bet they still make me write it on a whiteboard, though. (👋Hi future interviewer!)

I recently saw a twitter thread from @AccordionGuy discussing FizzBuzz implementations that don't use modulo division. @raganwald pointed out that those solutions were more interesting and more appropriate anyway since FizzBuzz the interview exercise is derived from FizzBuzz the children's counting game.

I thought that, yes, viewing this as a counting problem is more interesting than viewing it as a factorization problem. So, I implemented it. First in pseudocode, but then I kept going later in C#.

The Basics

Before I do anything else, just to make sure everyone knows what's going on, let me describe the FizzBuzz problem. The objective of FizzBuzz is to print out all of the counting numbers from 1 to 100. Except, for every 3rd number, you instead print "Fizz", and for every 5th number you print "Buzz". If a number is both a 3rd and a 5th number (ex. 15), then you print "FizzBuzz".

First I'll begin with a control loop, because I definitely need that. This is quasi-pseudocode and will not build, but I'm fine with that and I'll fix it shortly.

void PlayFizzBuzz()
{
    for(int i = 1; i <= 100; i++)
    {
        var result = "";
        
        if(i is fizz) result += "Fizz";
        if(i is buzz) result += "Buzz";
        if(String.isEmpty(result)) result += i.toString();

        Console.WriteLine(result);
    }
}

This is the basic logic for the FizzBuzz problem, and I'm sure this will be familiar to nearly everyone. The part we're interested in is everything that's being abstracted away by that is. I said I wanted a counting problem, so let's build a counter.

public class FizzBuzzCounter
{
    private int N, X;

    public FizzBuzzCounter(uint n)
    {
        // Assume n is non-zero
        N = n;
        X = N;
    }

    public int next()
    {
        if(X == 0) X = N;
        return --X;
    }
}

Great! If the counter is initialized with n = 3, the counter will count out 2, 1, 0, 2, 1, 0, etc. We can just plug this into the basic control loop, and the problem is solved. If you don't like that behavior, it's pretty simple to reimplement this where it counts from n...1, instead of n-1...0. Or up from 1...n. I personally like counting down to 0 because it avoids magic numbers in a lot of cases. It can also make doing things like producing exit codes or converting to booleans (nearly) automatic.

void PlayFizzBuzz()
{
    var fizz = new FizzBuzzCounter(3);
    var buzz = new FizzBuzzCounter(5);
    for(int i = 1; i <= 100; i++)
    {
        var result = "";
        
        if(fizz.next() == 0) result += "Fizz";
        if(buzz.next() == 0) result += "Buzz";
        if(String.isEmpty(result)) result += i.toString();

        Console.WriteLine(result);
    }
}

I may be basic, but I'm not that basic

FizzBuzz is a common interview question, and one of the ways it's most useful for that purpose is to serve as a framework to let the candidate work through a process of evolving requirements, asking questions and adapting along the way. So the candidate implements something, and then the interviewer asks for new enhancements and the candidate modifies the solution accordingly. A common enhancement is to allow for arbitrary combinations of numbers and words to be substituted in place of 3=Fizz. Which is to say, remove the harded coded magic numbers and strings, and allow the solution to take them as input. I'll do that by making them function arguments.

void PlayFizzBuzz(IEnumerable<(int n, string word)> fizzBuzzPairs = new[]{(3, "Fizz"), (5, "Buzz")}, uint max = 100)
{
    List<(string word, FizzBuzzCounter counter)> counters = fizzBuzzPairs
        .select(i => {
            return (i.word, new FizzBuzzCounter(i.n));
        });

    for(int i = 1; i <= max; i++)
    {
        var result = "";

        foreach(pair in counters)
        {
            if(pair.counter.Next() == 0) result += pair.word;
        }
        
        if(String.isEmpty(result)) result += i.toString();

        Console.WriteLine(result);
    }
}

Nice! Now I can dress up my counting game with any number or kind of replacer words that I can think of. And note the defensive coding: I used default arguments so that existing calls to this function will continue to work as they did without updates.

BUT, WAIT! That's a lot of tuples. Tuples are hard to maintain. They don't provide any semantic meaning or information.
And what if we don't want to print to the console?
And what about if the list of input pairs is out of order?
Pull request declined: needs work.

Okay, cool, let's do the work. We already have this FizzBuzzCounter class that handles the FizzBuzz logic. Let's let it encapsulate more of the meaning and intent of what we're doing, too.

public class FizzBuzzCounter : IComparable
{
    private int _n, _x;
    private string _word;

    public string Word => _word;

    public FizzBuzzCounter(uint n, string word)
    {
        // Assume n is non-zero
        // Assume word is not null or empty
        _n = n;
        _x = n;
        _word = word;
    }

    public int Next()
    {
        if(_x == 0) _x = _n;
        return --x;
    }

    public string NextWord()
    {
        return Next() == 0 ? Word : "";
    }

    int IComparable.CompareTo(FizzBuzzCounter obj)
    {
        return obj.N - this.N;
    }
}

There's several changes here, so let me go through them.

  1. We don't want to fuss around with lots of tuples. So we're removing them and encapsulating the word and the N value together in the counter. That means adding the string property and updating the constructor.
  2. We've added the word property, so we also need to provide access to it. This is done with the public Word property.
  3. The calling code can keep doing the FizzBuzz comparisons for itself, but it shouldn't have to. This class now has all of the information needed to do it, so it should also encapsulate that logic as well. Hence the NextWord() method.
  4. We want to ensure that replacement words are printed in the correct order, which means the list of counters needs to be sorted. Implementing IComparable makes them sortable with built-in functions.
IEnumerable<string> PlayFizzBuzz(IEnumerable<(int n, string word)> fizzBuzzPairs = new[]{(3, "Fizz"), (5, "Buzz")}, uint max = 100)
{
    List<FizzBuzzCounter counter> counters = fizzBuzzPairs
        .select(i => {
            return new FizzBuzzCounter(i.n, i.word);
        })
        .sort();

    for(int i = 1; i <= max; i++)
    {
        var result = "";

        foreach(counter in counters)
        {
            result += counter.NextWord();
        }
        
        if(String.isEmpty(result)) result += i.toString();

        yield return result;
    }
}

Again, there are several changes here:

  1. Got rid of some of those tuples. The builder line that converts the input to a list of FizzBuzzCounter objects is now much more readable.
  2. The same line also calls sort() to make sure it's sorted and the strings come out in the correct order.
  3. The foreach got a lot simpler. It just calls NextWord() now.
  4. The whole function has been changed to return an Enumerable<string>, and that is generated via yield return. The calling function can choose to print that to the console, or do anything else with it that you like.

I said I was basic, but what if was extra?

FizzBuzz is just an interview exercise. I'm not going to build this into some kind of high quality production system. But, again, the value of that interview exercise is to explore the domain together. So, what improvements could I make, if I wanted to work at FizzBuzz, inc.?

To start, I would minimize all of those tuples. But I would also want to do that without making the calling code depend on and construct it's own counter objects. It could, of course, but I don't think it should have to. So I would still need to convert from (int, string) to FizzBuzzCounter. And I would do that by implementing a user-defined conversion operator in the counter class to make that conversion automatic.

There's the places I called out where we just assume that input data is valid. That's not going to pass muster at FizzBuzz Incorporated, either. So I would need to add some validation and error handling, mostly in the form of thrown ArgumentException.

Next, I can't help but notice we're doing some string concatenation. You might be tempted to replace that with a StringBuilder. That's an option, but I would avoid it. StringBuilder has a significant initialization cost, and it's just not as readable, in my opinion. What I would do instead, is String.Join. This is the perfect scenario for it. That foreach is almost exactly logically equivalent to a Linq function that will produce an Enumerable<string>. Then you can just Join that, like so:

var result = String.Join("", counters.select(c => c.NextWord()));

And of course, what article from me would be complete without exhorting you to write unit tests? I did not do that, in this case. It would be straightforward, of course. I will leave that as an exercise to the reader.

P.S. for the record, Joey deVilla did much the same thing, much faster. If you're interested in his take, you can find it here.


Cover Photo by Sam Cernik on Unsplash