Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
Fizz Buzz
The famous screening question. Print 1…n, but multiples of 3 become "Fizz", of 5 become "Buzz", and of both become "FizzBuzz". Easy — the catch is testing divisibility in the right order.
The problem
Return a list of length n where, for each i from 1 to n: it is "FizzBuzz" if i is divisible by both 3 and 5, "Fizz" if only by 3, "Buzz" if only by 5, otherwise the number as a string.
Your turn — write it
Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? open the hints under the problem, or Reveal solution is one click away.
Implement fizzbuzz(n): return the list of length n following the Fizz / Buzz / FizzBuzz rules. Watch the order — test "both" before either single case.
Examples and constraints 4 examples
n = 5["1", "2", "Fizz", "4", "Buzz"]n = 15["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"]n = 3["1", "2", "Fizz"]n = 1["1"]- 1 ≤ n ≤ 10⁴
- Every element is a string — including the plain numbers.
- Check divisibility by 15 first (or combine the two booleans), or the FizzBuzz case never fires.
Hints 4 ideas
- Loop
ifrom 1 to n inclusive. - Divisible by both 3 and 5 is the same as divisible by 15 — check that first.
- Otherwise check 3, then 5, then fall back to the number.
- Everything in the list is a string, so convert the plain numbers too.
Approach, complexity & discussion — open after you solve
Start from brute force
The obvious first draft tests the rules in the order the problem states them: if i % 3 == 0 appends “Fizz”, elif i % 5 == 0 appends “Buzz”, elif i % 15 == 0 appends “FizzBuzz”, and otherwise the number. It runs in O(n) time with O(n) space for the list, and it is right for 14 of the first 15 numbers. It is wrong at 15 and at every multiple of 15 after it, because every multiple of 15 is also a multiple of 3: the first branch catches it, and an elif chain never looks further. Up to 10,000 this draft never prints “FizzBuzz” once. Speed was never the problem; the order of overlapping conditions is.
The approach
The insight is that the rules overlap, and an if/elif chain is decided by its first true branch, so the most specific rule has to come first. Divisible by both 3 and 5 is the same as divisible by 15, because 3 and 5 share no factor and 15 is their least common multiple, so one test, i % 15 == 0, covers the combined case. The invariant: after the iteration for i, out holds exactly i strings and out[i - 1] is the answer for i.
The reference loops for i in range(1, n + 1), so 1 and n are both included and there is no entry for 0. It checks i % 15 == 0 first and appends “FizzBuzz”, then i % 3 == 0 for “Fizz”, then i % 5 == 0 for “Buzz”, and otherwise appends str(i), because every element must be a string. An equally correct shape builds the word from two independent checks and falls back to the number when the word is empty; that version has no ordering trap at all.
Walk through an example
The second example, n = 15, traced with the three remainders the chain can test; its first five rows are the first example.
| step | i % 15 | i % 3 | i % 5 | branch taken | appended |
|---|---|---|---|---|---|
i = 1 | 1 | 1 | 1 | else | "1" |
i = 2 | 2 | 2 | 2 | else | "2" |
i = 3 | 3 | 0 | 3 | i % 3 == 0 | "Fizz" |
i = 4 | 4 | 1 | 4 | else | "4" |
i = 5 | 5 | 2 | 0 | i % 5 == 0 | "Buzz" |
i = 6 to 14 | non-zero | 0 at 6, 9, 12 | 0 at 10 | Fizz at 6, 9, 12; Buzz at 10; else at 7, 8, 11, 13, 14 | "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14" |
i = 15 | 0 | 0 | 0 | i % 15 == 0, tested first | "FizzBuzz" |
| return | — | — | — | — | 15 strings, the last one "FizzBuzz" |
Complexity
Reference: O(n) time, a fixed number of modulo checks and one append per number, and O(n) space for the returned list; strictly, the digits written grow as O(n log n) characters. Nothing can beat O(n), because the output has n entries. Naive chain: the same O(n) time and space. Its problem is order, not cost.
Edge cases to test
n = 1:["1"]. The loop starts at 1; starting at 0 adds a “FizzBuzz” for 0, because0 % 15 == 0, and drops the last number.n = 15: the last entry is “FizzBuzz”. A chain that tests 3 first returns “Fizz” there.n = 30: indices 14 and 29 are both “FizzBuzz”. Every multiple of 15 must reach the first branch, not just 15 itself.n = 2:["1", "2"], strings rather than the integers[1, 2]. A list of ints fails the equality check.n = 10⁴, the upper bound: 10,000 strings, of which 666 are “FizzBuzz”, 2,667 “Fizz”, 1,334 “Buzz” and 5,333 plain numbers.n = 0, outside the stated range:range(1, 1)is empty, so the reference returns[]rather than raising.
Common mistakes
- Checking 3 and 5 before 15, so multiples of 15 print “Fizz” instead of “FizzBuzz”.
- Off-by-one on the range (starting at 0, or missing the last number).
- Appending the plain numbers as integers:
fizzbuzz(2)must be["1", "2"], and[1, 2]is not equal to it. - Over-engineering a five-line problem: a rule table earns its place once there are several rules, but for two rules a plain loop that is obviously correct is the better answer.
Follow-ups an interviewer asks next
- Add a rule: multiples of 7 print “Bazz”.
- The
if/elifchain stops scaling, because three rules have seven non-empty combinations, each needing its own branch in the right order. Build the word instead: keep an ordered list of(divisor, word)pairs, join the words whose divisor dividesi, and fall back tostr(i)when nothing matched. Then 21 gives “FizzBazz”, 35 “BuzzBazz” and 105 “FizzBuzzBazz” with no extra code. - Solve it without the modulo operator.
- Keep two countdown counters, one starting at 3 and one at 5. Each iteration decrements both; when one reaches zero, add its word and reset it to 3 or 5. That replaces division with subtraction and produces exactly the same list, which you can confirm by comparing it with the modulo version for every n up to 10,000.
- n is a billion. Do not build the list.
- Yield each string from a generator, so the extra memory stays O(1) instead of O(n). You can also use the fact that the pattern repeats every 15 numbers: precompute the 15-entry cycle once and look up
(i - 1) % 15, writingstr(i)wherever the cycle has no word.
Where this shows up
FizzBuzz is the classic screening warm-up — it filters for basic control flow and, subtly, for handling the combined case in the right order. Trivial as it is, the divisibility-ordering trap is a real one that catches rushed candidates.
Reference solution
Reference solution — Python
def fizzbuzz(n):
out = []
for i in range(1, n + 1):
if i % 15 == 0: # both 3 and 5 — check this FIRST
out.append("FizzBuzz")
elif i % 3 == 0:
out.append("Fizz")
elif i % 5 == 0:
out.append("Buzz")
else:
out.append(str(i)) # plain number, as a string
return out
print(fizzbuzz(5)) # -> ['1', '2', 'Fizz', '4', 'Buzz']Reference solution — TypeScript
function fizzbuzz(n: number): string[] {
const out: string[] = [];
for (let i = 1; i <= n; i++) {
if (i % 15 === 0) { // both 3 and 5 — check this FIRST
out.push("FizzBuzz");
} else if (i % 3 === 0) {
out.push("Fizz");
} else if (i % 5 === 0) {
out.push("Buzz");
} else {
out.push(String(i)); // plain number, as a string
}
}
return out;
}
console.log(fizzbuzz(5)); // -> ["1","2","Fizz","4","Buzz"]Questions about Fizz Buzz
What is the FizzBuzz problem?
FizzBuzz asks you to go through the numbers from 1 to n and replace multiples of 3 with Fizz, multiples of 5 with Buzz, and multiples of both with FizzBuzz, leaving every other number as it is. It began as a children’s counting game and became a well-known programming screening question, because in a few lines it tests a loop, the modulo operator and the order of overlapping conditions. In this version you return the results as a list of strings, so the plain numbers are converted too.
Why do you check divisibility by 15 first in FizzBuzz?
Because the conditions overlap and an if-elif chain stops at the first true branch. Every multiple of 15 is also a multiple of 3, so if the test for 3 comes first, 15 gets Fizz and the FizzBuzz branch is never reached. Checking 15 first handles the most specific case before the general ones. Divisible by both 3 and 5 is the same as divisible by 15 because 3 and 5 share no common factor, which makes 15 their least common multiple.
How do you solve FizzBuzz without checking for 15?
Build the output from independent checks. Start with an empty string, add Fizz if the number is divisible by 3, add Buzz if it is divisible by 5, and if the string is still empty use the number itself. For 15 both checks fire and the two words join into FizzBuzz on their own. This version has no ordering trap, and adding another rule, such as Bazz for multiples of 7, is one more check instead of a new set of combined cases.
What is the time complexity of FizzBuzz?
Linear in n. Each number from 1 to n gets a fixed number of modulo checks and one append, so the loop does O(n) work, and the returned list holds n strings, so it also uses O(n) space. No algorithm can do better, because the output itself has n entries. If the strings are streamed with a generator instead of collected in a list, the extra memory drops to a constant. At the upper limit of n = 10,000, the list contains 666 FizzBuzz entries.
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Challenges
- Two SumThe classic warm-up: find the two numbers that add up to a target. Brute force is O(n²) — a hash map gets you to one pass, O(n). Solve it in Python or TypeScript, right in your browser, with hidden tests and a reveal-solution button.Read →
- Valid ParenthesesThe canonical stack problem: decide whether every bracket is closed by the right type, in the right order. A stack turns nested matching into a single pass. Solve it in Python or TypeScript with hidden tests.Read →
- SoftmaxThe function at the end of every classifier and language model: turn raw scores (logits) into a probability distribution. Implement the numerically stable version so big logits do not overflow. Solve it in Python or TypeScript.Read →
- Cosine SimilarityThe measure behind every embedding search and RAG system: how aligned are two vectors, ignoring their length? Dot product over the product of magnitudes — 1 identical, 0 orthogonal, -1 opposite. Solve it in Python or TypeScript.Read →
- Top-K RetrievalThe core of the "R" in RAG: given a query embedding and a set of document embeddings, return the indices of the k most similar docs by cosine similarity, with a stable tie-break. Solve it in Python or TypeScript.Read →
- Token-Level F1The metric behind QA evaluation (SQuAD and friends): how well does a predicted answer overlap a reference as a bag of words? Compute token precision and recall, then their harmonic-mean F1. Solve it in Python or TypeScript.Read →