Skip to content
aviral gupta

// B2.5 · ~40 min · Beginner

Build: FizzBuzz variants and a menu with match

After this build you can combine every tool of module B2 in one program: FizzBuzz and its variants, and a text menu that loops with while True and picks commands with match.

Lesson 5 of 5 in B2 Control flow

End of the module

You will be able to

  • Write FizzBuzz and its variants with for, range, if and match
  • Drive a text menu with while True, match, break and continue
  • Spot the classic slips in such programs: condition order, off-by-one ranges and text in guards
  1. Warm-up · Activity 1 of 7

    Warm-up from module B2: which statements are true? Pick all that apply.

    Select all that apply.

  2. Predict · Activity 2 of 7

    A FizzBuzz variant. Predict before you read on: what does this print?

    for i in range(1, 7):
        if i % 3 == 0:
            print("Fizz", end=" ")
        elif i % 2 == 0:
            print("Even", end=" ")
        else:
            print(i, end=" ")
  3. Practice · Activity 3 of 7

    Fill in the pattern that matches any remainder, without binding a name, so that 9 prints Fizz.

    for i in [15, 9, 10, 7]:
        match [i % 3, i % 5]:
            case [0, 0]:
                print("FizzBuzz")
            case [0, ____]:
                print("Fizz")
            case [_, 0]:
                print("Buzz")
            case _:
                print(i)
    case [0, ]:
  4. Practice · Activity 4 of 7

    A menu loop over a list of commands. What does this print?

    log = []
    for command in ["help", "", "add", "quit", "add"]:
        match command:
            case "":
                continue
            case "quit":
                break
            case "help" | "?":
                log.append("help")
            case _:
                log.append(command)
    print(log)
  5. Practice · Activity 5 of 7

    Put the steps of one round of a menu loop in order.

    1. 1.Run the command, or break in the quit case
    2. 2.Read a line and split it into words
    3. 3.Match the list of words against the command patterns
    4. 4.Start the round: while True:
  6. Brain teaser · Activity 6 of 7

    Brain teaser. After the loop, what does the last line print?

    for i in range(1, 16):
        match [i % 3, i % 5]:
            case [0, _]:
                word = "Fizz"
            case [_, 0]:
                word = "Buzz"
            case [0, 0]:
                word = "FizzBuzz"
            case _:
                word = ""
    print(word)
  7. Apply · Activity 7 of 7

    Mini-task. Extend FizzBuzz with a third rule: multiples of 7 add Bazz. The words combine, so 21 prints FizzBazz and 105 prints FizzBuzzBazz. Read n with int(input()) and print one line per number from 1 to n. Hint: build the word with three separate ifs.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A FizzBuzz lab with a menu

This program puts the whole module together. A while True menu reads commands: help, fizzbuzz N prints FizzBuzz up to N using match, first N searches for the first FizzBuzz with break and a loop else, and quit ends the program. Empty lines are skipped, and a guard with isdigit() turns away arguments that are not numbers. The input below feeds it seven lines; change them and run it again.

main.py

# A small FizzBuzz lab, driven by a menu. Each input line is one command.
while True:
    words = input().split()
    match words:
        case []:
            continue  # an empty line: ask again
        case ["quit" | "q"]:
            print("Bye!")
            break
        case ["help"]:
            print("Commands: fizzbuzz N, first N, help, quit")
        case ["fizzbuzz", n] if n.isdigit():
            for i in range(1, int(n) + 1):
                match [i % 3, i % 5]:
                    case [0, 0]:
                        print("FizzBuzz", end=" ")
                    case [0, _]:
                        print("Fizz", end=" ")
                    case [_, 0]:
                        print("Buzz", end=" ")
                    case _:
                        print(i, end=" ")
            print()
        case ["first", n] if n.isdigit():
            # The first number up to n that is a FizzBuzz, if there is one.
            for i in range(1, int(n) + 1):
                if i % 15 == 0:
                    print("First FizzBuzz:", i)
                    break
            else:
                print("No FizzBuzz up to", n)
        case [command, *_]:
            print("Unknown command:", command)

input.txt

help
fizzbuzz 15

first 10
first 40
fizzbuzz ten
quit

Run it with

python main.py < input.txt

Output

Commands: fizzbuzz N, first N, help, quit
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
No FizzBuzz up to 10
First FizzBuzz: 15
Unknown command: fizzbuzz
Bye!
  • The empty third line gives [], so case [] continues without printing anything.
  • first 10 finds no multiple of 15, so the loop ends without break and its else runs.
  • fizzbuzz ten fails the isdigit() guard, so match falls through to case [command, *_].
  • The print() after the inner loop ends the line that the end=" " prints left open.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Exercises

Exercise 1 of 3

Step 1: classic FizzBuzz

The program reads a whole number n. Print the numbers from 1 to n, one per line, but print Fizz for multiples of 3, Buzz for multiples of 5 and FizzBuzz for multiples of both. Use if and elif, or match on [i % 3, i % 5]. For 0, print nothing.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    Inside the loop, decide what to print for i with an if, elif and else chain.

  2. Hint 2

    Test for both first: i % 15 == 0 (or i % 3 == 0 and i % 5 == 0), or 15 stops at Fizz.

  3. Hint 3

    With match: match [i % 3, i % 5]: then case [0, 0]:, case [0, _]:, case [_, 0]: and case _:.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

n = int(input())

for i in range(1, n + 1):
    if i % 15 == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        print(i)
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

n = int(input())

for i in range(1, n + 1):
    # Replace multiples of 3 with Fizz, of 5 with Buzz, of both with FizzBuzz.
    print(i)

test_main.py

from learnrun import run_main


def fizzbuzz(n):
    return run_main(f"{n}\n").split()


def test_first_five():
    """1 to 5: 1 2 Fizz 4 Buzz"""
    got = fizzbuzz(5)
    assert got == ["1", "2", "Fizz", "4", "Buzz"], f"for 5 the program printed {got!r}"


def test_fifteen():
    """15 is FizzBuzz, not Fizz"""
    got = fizzbuzz(15)
    assert len(got) == 15, f"for 15 the program printed {len(got)} lines, expected 15"
    assert got[-1] == "FizzBuzz", f"the 15th line is {got[-1]!r}, expected 'FizzBuzz' (check the order of your tests)"


def test_to_thirty():
    """Lines 25 to 30 are Buzz 26 Fizz 28 29 FizzBuzz"""
    got = fizzbuzz(30)[24:]
    assert got == ["Buzz", "26", "Fizz", "28", "29", "FizzBuzz"], f"lines 25 to 30 are {got!r}"


def test_zero():
    """0 prints nothing"""
    got = fizzbuzz(0)
    assert got == [], f"for 0 the program printed {got!r}, expected nothing"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 2 of 3

Step 2: stop at the first FizzBuzz

Change FizzBuzz for n so that it stops right after printing the first FizzBuzz: nothing after it is printed. If the loop reaches n without any FizzBuzz, print no FizzBuzz as a last line. Use match on [i % 3, i % 5], break, and else on the for loop.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    break inside a case ends the for loop around the match.

  2. Hint 2

    Put break right after print("FizzBuzz"), in the same case.

  3. Hint 3

    Write else: print("no FizzBuzz") at the indentation of for: it runs only when no break happened.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

n = int(input())

for i in range(1, n + 1):
    match [i % 3, i % 5]:
        case [0, 0]:
            print("FizzBuzz")
            break
        case [0, _]:
            print("Fizz")
        case [_, 0]:
            print("Buzz")
        case _:
            print(i)
else:
    print("no FizzBuzz")
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

n = int(input())

for i in range(1, n + 1):
    match [i % 3, i % 5]:
        case [0, 0]:
            print("FizzBuzz")
        case [0, _]:
            print("Fizz")
        case [_, 0]:
            print("Buzz")
        case _:
            print(i)
# Stop after the first FizzBuzz; otherwise print "no FizzBuzz".

test_main.py

from learnrun import run_main


def lines(n):
    return run_main(f"{n}\n").splitlines()


def test_stops_at_fifteen():
    """For 20 it stops after line 15, FizzBuzz"""
    got = lines(20)
    assert len(got) == 15, f"for 20 the program printed {len(got)} lines, expected 15"
    assert got[-1] == "FizzBuzz", f"the last line is {got[-1]!r}, expected 'FizzBuzz'"


def test_no_fizzbuzz():
    """For 7 all seven lines appear, then no FizzBuzz"""
    got = lines(7)
    assert got == ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "no FizzBuzz"], f"for 7 the program printed {got!r}"


def test_exactly_fifteen():
    """For 15 the loop breaks, so no FizzBuzz is not printed"""
    got = lines(15)
    assert "no FizzBuzz" not in got, "for 15 the program printed 'no FizzBuzz', but it did find one"
    assert got[-1] == "FizzBuzz", f"the last line is {got[-1]!r}, expected 'FizzBuzz'"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 3 of 3

Step 3: a calculator menu

Build a menu that keeps a total, starting at 0. Read lines until quit. add N and sub N change the total when N is a whole number (check with N.isdigit() in a guard). show prints the total. quit prints bye and ends. Skip empty lines, and print unknown command for anything else.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    Each round: words = input().split(), then match words: with one case per command.

  2. Hint 2

    case ["add", n] if n.isdigit(): runs only when n is a number; "add x" then falls through to case _.

  3. Hint 3

    case []: continue skips empty lines; case ["quit"]: prints bye and breaks.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

total = 0

while True:
    words = input().split()
    match words:
        case []:
            continue
        case ["add", n] if n.isdigit():
            total = total + int(n)
        case ["sub", n] if n.isdigit():
            total = total - int(n)
        case ["show"]:
            print(total)
        case ["quit"]:
            print("bye")
            break
        case _:
            print("unknown command")
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

total = 0

while True:
    words = input().split()
    # Match words: add N, sub N, show, quit, an empty line, anything else.
    break

test_main.py

from learnrun import run_main


def menu(*commands):
    return run_main("\n".join(commands) + "\n").splitlines()


def test_add_and_show():
    """add 5, add 3, show prints 8"""
    got = menu("add 5", "add 3", "show", "quit")
    assert got == ["8", "bye"], f"the program printed {got!r}, expected ['8', 'bye']"


def test_sub():
    """add 10, sub 4, show prints 6"""
    got = menu("add 10", "sub 4", "show", "quit")
    assert got == ["6", "bye"], f"the program printed {got!r}, expected ['6', 'bye']"


def test_empty_lines():
    """Empty lines are skipped; the total starts at 0"""
    got = menu("", "show", "", "quit")
    assert got == ["0", "bye"], f"the program printed {got!r}, expected ['0', 'bye']"


def test_unknown():
    """Unknown commands and non-numbers are refused"""
    got = menu("jump", "add x", "add", "quit")
    assert got == ["unknown command"] * 3 + ["bye"], f"the program printed {got!r}"


def test_quit_stops():
    """Nothing after quit is read"""
    got = menu("quit", "add 1", "show")
    assert got == ["bye"], f"the program printed {got!r}, expected only ['bye']"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Common mistakes

Adding a number to text

i = 3
print("Fizz" + i)

What Python prints

TypeError: can only concatenate str (not "int") to str

Why, and the fix

+ joins two strings or adds two numbers, but not one of each. Print them side by side, print("Fizz", i), or turn the number into text first with str(i).

Converting the whole line

line = "add 5"  # what input() gives you
n = int(line)

What Python prints

ValueError: invalid literal for int() with base 10: 'add 5'

Why, and the fix

int() needs text that is only a number. Split the line first and convert just the argument: words = line.split(), then int(words[1]), or capture it with case ["add", n]: and call int(n).

Comparing captured text with a number

match "add 5".split():
    case ["add", n] if n > 0:
        print("adding", n)

What Python prints

TypeError: '>' not supported between instances of 'str' and 'int'

Why, and the fix

Patterns capture parts of a split line as strings, so n is "5", not 5. Convert in the guard, if int(n) > 0, and when the text might not be a number, check n.isdigit() first: if n.isdigit() and int(n) > 0.

Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

FizzBuzz, two ways

FizzBuzz prints the numbers from 1 to n, but Fizz for multiples of 3, Buzz for multiples of 5 and FizzBuzz for multiples of both. With if and elif, the test for both must come first, or 15 stops at Fizz. With match, you can match the pair of remainders: case [0, 0]: is FizzBuzz, case [0, _]: is Fizz, case [_, 0]: is Buzz. A third way builds the word: add "Fizz" and "Buzz" with two separate ifs, then print(word or i).

A menu loop

A text menu repeats until the user quits, so it is a while True: loop. Each round reads a line, splits it into words and matches the list. An empty line gives [], which case []: can skip with continue. case ["quit"]: prints a goodbye and ends the loop with break. Commands with arguments, such as ["add", n], capture the argument as text; a guard such as if n.isdigit() checks it before int(n) runs. case _: answers everything else.

Where these programs usually go wrong

Order: a wider condition or pattern before a narrower one hides it, and a wildcard or bare name before other cases is a SyntaxError. Ranges: range(1, n) stops at n - 1, so the last number needs range(1, n + 1). Text and numbers: input() and split() give strings, so "Fizz" + i, int("add 5") and n > 0 on a string all fail. Convert first, and check with isdigit() when the text might not be a number.

Sources

Last reviewed September 29, 2026