Projects · Lesson 25.2

FizzBuzz

Source
Count to 100, saying Fizz for multiples of 3, Buzz for multiples of 5 and FizzBuzz for both.
You'll need: Parts 1–3 — this project is a checkpoint for Control flow, and leans on Arithmetic, Else if and For.

FizzBuzz is a counting game: go from 1 to 100, but say "Fizz" instead of every multiple of 3, "Buzz" instead of every multiple of 5, and "FizzBuzz" instead of every multiple of both. It began as a children's game for practising division and became the most famous little test in programming — not because it is hard, but because it is so easy to get almost right.

The program needs only Parts 1–3. It also keeps a tally of each kind of answer, which turns out to be a handy way of checking that the logic is right.

How it is put together

Everything happens inside one for loop. Each pass asks a chain of questions about n, prints one answer, counts it, and then decides what goes after it — a space or a line break:

flowchart LR
    n(["n from 1 to 100"]) --> q15{"n % 15 == 0?"}
    q15 -- "yes" --> fb["FizzBuzz"]
    q15 -- "no" --> q3{"n % 3 == 0?"}
    q3 -- "yes" --> f["Fizz"]
    q3 -- "no" --> q5{"n % 5 == 0?"}
    q5 -- "yes" --> b["Buzz"]
    q5 -- "no" --> num["the number"]
PieceLessons it uses
n % 3 == 0Arithmetic (%), Comparison
The chain of questionsElse if
Walking 1 to 100For, Range
The talliesMutable, Assignment (+=)
Ten answers to each lineIf, Console (Print and PrintLine)

"Divides exactly" is a remainder of zero

% gives the remainder of a division. When a number divides by 3 exactly, nothing is left over, so n % 3 == 0 reads as "n is a multiple of 3". A number that is a multiple of both 3 and 5 is a multiple of 15, so one test covers "both":

if n % 15 == 0 {
    Print("FizzBuzz");
    fizzBuzzes += 1;
} else if n % 3 == 0 {
    Print("Fizz");
    fizzes += 1;
} else if n % 5 == 0 {
    Print("Buzz");
    buzzes += 1;
} else {
    Print("{}", n);
}

The order of the questions is the whole trick

An else if chain runs the first branch whose condition holds and skips the rest. Fifteen is a multiple of 3, so a chain that asks about 3 first answers "Fizz" for 15 and never gets as far as "FizzBuzz". The most specific question has to come first; the general ones follow.

That is also why the tallies are worth printing. In a correct run there are 6 FizzBuzzes. If you move the % 3 test to the top of the chain, the program still compiles and still prints a hundred answers, but the tally reports Fizz 33 times and FizzBuzz 0 times — every FizzBuzz has been swallowed by Fizz.

Ten to a line

After each answer the loop decides what comes next. Every tenth number ends the line; every other one is followed by a space:

if n % 10 == 0 {
    PrintLine();
} else {
    Print(" ");
}

Print writes without ending the line, so the answers build up across it until PrintLine() with no arguments ends it. The same % test that finds the multiples of 3 here finds every tenth n.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// FizzBuzz: count from 1 to 100, but say "Fizz" instead of every multiple of 3, "Buzz" instead
// of every multiple of 5, and "FizzBuzz" instead of every multiple of both.
//
// It began as a children's game for practising division, and became the most famous little test
// in programming because it is so easy to get almost right. The trap is the order of the
// questions. Fifteen is a multiple of 3, so a program that asks about 3 first says "Fizz" and
// never gets as far as "FizzBuzz". The multiple of both has to be checked before either one.
//
// Everything here comes from the first three parts: `%` gives the remainder of a division, so
// `n % 3 == 0` means "n divides by 3 exactly"; an `if` / `else if` chain picks the first question
// that answers yes; and a `for` loop walks the numbers.
import Io::{ Print, PrintLine };

func Main() -> int {
    var fizzes = 0;
    var buzzes = 0;
    var fizzBuzzes = 0;

    for n in 1..=100 {
        // A multiple of both 3 and 5 is a multiple of 15, so one test covers it.
        if n % 15 == 0 {
            Print("FizzBuzz");
            fizzBuzzes += 1;
        } else if n % 3 == 0 {
            Print("Fizz");
            fizzes += 1;
        } else if n % 5 == 0 {
            Print("Buzz");
            buzzes += 1;
        } else {
            Print("{}", n);
        }

        // Ten to a line keeps the output short: a space between entries, and a line break after
        // every tenth one.
        if n % 10 == 0 {
            PrintLine();
        } else {
            Print(" ");
        }
    }

    PrintLine();
    PrintLine("Fizz      {} times", fizzes);
    PrintLine("Buzz      {} times", buzzes);
    PrintLine("FizzBuzz  {} times", fizzBuzzes);
    PrintLine("numbers   {} times", 100 - fizzes - buzzes - fizzBuzzes);
    return 0;
}

Run it

cd Examples/Projects/FizzBuzz
rux run
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 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz
41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz
Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz
61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz
71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz
Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz
91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz

Fizz      27 times
Buzz      14 times
FizzBuzz  6 times
numbers   53 times

Common mistakes

Asking the general question first.
if n % 3 == 0 before if n % 15 == 0 is still valid code, so the compiler cannot help — 15, 30, 45 and the rest come out as Fizz. The tally gives it away: Fizz 33 times, FizzBuzz 0 times. Always order a chain from the most specific case to the least.
= where you meant ==.
if n % 15 = 0 is an assignment, not a comparison, and is refused: error: operator '=' requires an assignable target, but its left operand has type 'int'. A condition compares with ==.
A tally declared with let.
The counters change on every pass, so they need var. With let fizzes = 0; the line fizzes += 1; fails with error: cannot modify immutable variable 'fizzes'.

Try it yourself

  1. Add a fourth rule: say "Bazz" for multiples of 7. Where in the chain does each new test have to go, and what should a multiple of 3, 5 and 7 print?
  2. Count to 30 instead of 100. The last line of the tally computes the plain numbers as 100 - fizzes - buzzes - fizzBuzzes — what else has to change so the report stays right?
  3. Print five answers to a line instead of ten.
  4. Rewrite the chain so it never mentions 15: test 3 and 5 on their own, and use && for the case where both hold. Check that the tallies come out the same.

Learn more