The “Fizz-Buzz test” is an interview question designed to help filter out the 99.5% of programming job candidates who can’t seem to program their way out of a wet paper bag. (wiki.c2.com)
Sometimes, even the simplest problem can become tricky.
That is the case for the FizzBuzz interview question.
Write a program that prints the numbers from 1 to 100.
But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”.
For numbers which are multiples of both three and five print “FizzBuzz”.
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz...
Pretty simple, isn’t it? Well, this actually can be harder than you think!
Every programming language provides its own way and tools to handle that kind of problems… but how can we measure skills with such a simple task?
Is it really important to ask super-complex questions about parallel programming, algorithms, type theory, graph theory… ?
Of course, these are important topics and should definitely be tested, but when you’re screening a candidate, maybe you want to test just his developer skills. And that’s where the FizzBuzz question comes to help:
for (let i = 1; i < 100; i++) {
if (i % 3 === 0 && i % 5 === 0) {
console.log("FizzBuzz");
} else if (i % 3 === 0) {
console.log("Fizz");
} else if (i % 5 === 0) {
console.log("Buzz");
} else {
console.log(i);
}
}
Well, that is not the cleanest solution... but it works!
While the candidate is writing the code above, you’ll be able to understand a bunch of things:
Given the example above, answers may be… yes, nope, nope, nope.
So now, you may ask the candidate why is he writing multiple console.log statements? Imagine that you’re about to add a new super-cool logging library to your software... you have to replace four console.log statements!
for (let i = 1; i < 100; i++) {
let output = "";
if (i % 3 === 0 && i % 5 === 0) {
output += "FizzBuzz";
} else if (i % 3 === 0) {
output += "Fizz";
} else if (i % 5 === 0) {
output += "Buzz";
} else {
output = i;
}
console.log(output);
}
Ok, great! Still working! But there is still a big problem about maintainability.
What if we want to add more control structures? Let’s say we want to print “Foo” when a number is multiple of 7 and “Bar” when it’s multiple of 11...
for (let i = 1; i < 100; i++) {
let output = "";
if (i % 3 === 0 && i % 5 === 0) {
output += "FizzBuzz";
} else if (i % 3 === 0) {
output += "Fizz";
} else if (i % 5 === 0) {
output += "Buzz";
} else if (i % 7 === 0) {
output += "Foo";
} else if (i % 11 === 0) {
output += "Bar";
} else {
output = i;
}
console.log(output);
}
Bad, bad, bad idea... For instance, now we got 21 that should print both “Foo” and “Fizz”... but it’s printing only “Fizz”! So we have to add more control structures... that is unmaintainable.
Another great question would be “why are you using multiple else if statements that checks against a simple numeric value? Is there a better alternative?
for (let i = 1; i < 100; i++) {
let output = "";
switch (true) {
case (i % 3 === 0 && i % 5 === 0):
output += "FizzBuzz";
break;
case (i % 3 === 0):
output += "Fizz";
break;
case (i % 5 === 0):
output += "Buzz";
break;
default:
output = i;
}
console.log(output);
}
switch/case
to rescue! But we still have the scalability and maintainability problem! Let’s fix this:
function isMultiple(num, mod) {
return num % mod === 0;
}
for (let i = 1; i < 100; i++) {
let output = "";
switch (true) {
case isMultiple(i, 15):
output += "FizzBuzz";
break;
case isMultiple(i, 3):
output += "Fizz";
break;
case isMultiple(i, 5):
output += "Buzz";
break;
default:
output = i;
}
console.log(output);
}
Great! So now, if you want to add one more condition to your switch/case statement, you can just find the minimum common multiple and test it against the current i number. Problem solved!
But now… let’s make the things harder. How would you approach the same problem without using loops?
That is a tricky question and will show the candidate deep knowledge of that particular programming language. I mean, there are just a few programming languages that doesn’t support loops, so we’re all pretty addicted to them!
Finding an alternative won’t be simple!
function isMultiple(num, mod) {
return num % mod === 0;
}
const main = [...Array(100)].map((_, i) => {
const num = i + 1;
switch(true) {
case isMultiple(i, 15): return "FizzBuzz";
case isMultiple(i, 3): return "Fizz";
case isMultiple(i, 5): return "Baz";
default: return num;
}
}).join("\n");
Spread Operator + Map Method and here we are! We changed a lot our implementation from start ‘till now, huh?
Let’s analyze the code above:
From here, we can test our candidate’s testing skills or even TDD approach. In fact, we could give him the following tests and see how he adapts his code to pass every test.
I know, TDD approach should declare tests before the implementation itself... but that can be a great case to see how a candidate refactors his code after seeing some tests.
const lib = require("./main");
describe("FizzBuzz", () => {
it(`Should return "FizzBuzz"
when the number is evenly divisible by 15,
"Fizz" when it's evenly divisible by 3 and
"Buzz" when it's evenly dibisible by 5`,
() => {
expect(lib.FizzBuzz(1)).toBe(1);
expect(lib.FizzBuzz(3)).toBe("Fizz");
expect(lib.FizzBuzz(5)).toBe("Buzz");
expect(lib.FizzBuzz(7)).toBe(7);
expect(lib.FizzBuzz(15)).toBe("FizzBuzz");
expect(lib.FizzBuzz(30)).toBe("FizzBuzz");
expect(lib.FizzBuzz(33)).toBe("Fizz");
});
});
describe("IsMultiple", () => {
it(`Should return true if the number is a multiple
of a given argument. Otherwise it should return false.`,
() => {
expect(lib.isMultiple(10, 5)).toBeTruthy();
expect(lib.isMultiple(10, 2)).toBeTruthy();
expect(lib.isMultiple(60, 20)).toBeTruthy();
expect(lib.isMultiple(65, 15)).toBeTruthy();
expect(lib.isMultiple(319, 4)).toBeFalsy();
expect(lib.isMultiple(9, 10)).toBeFalsy();
});
});
Ok so these are our tests! Let’s se how the candidate should refactor its code:
function isMultiple(num, mod) {
return num % mod === 0;
}
function FizzBuzz(num) {
switch(true) {
case isMultiple(i, 15): return "FizzBuzz";
case isMultiple(i, 3): return "Fizz";
case isMultiple(i, 5): return "Baz";
default: return num;
}
}
const main = [...Array(100)].map((_, i) => FizzBuzz(i + 1));
module.exports = {
isMultiple,
FizzBuzz
}
Great! What do we know now?
If you want to play with that code, I made a little sandbox with tests available here: https://repl.it/@micheleriva/Jest-FizzBuzz.
So as you can see, that simple task can show a lot about a candidate. Therefore, it should be a great question to ask during an interview.
We can learn a lot about a programmer while he’s approaching that simple problem: does he care about clean code? Maintainability? Scalability? Modularity? Testing? I’ve already mentioned five huge topics!
How much more can you understand from a programmer, when such a simple task is given?
Did you like this article? Consider becoming a Patron!