Several years ago, the Oatmeal did a fabulously hilarious poster that showed how a website redesign starts off on the right foot– only to become completely derailed into a horrible abomination by client feedback and endless revisions.
Web Development is more efficient. We don’t let client feedback mess up our code. Instead, we screw it all up by ourselves.
Ladies and Gentlemen, I present to you the “FizzBuzz Problem.”
Disclosure: I went through a job search in 2014, and no one asked me to solve the FizzBuzz problem in a job interview. The only reason I’ve heard of it is because it was mentioned on Quora. Personally, I think it sounds condescending and alienating, but it makes a great example of “HOW A SIMPLE ALGORITHM GOES STRAIGHT TO HELL!”
Shall we get started?
The objectives in the FizzBuzz Problem are simple:
1) write an algorithm that prints the numbers 1 – 100.
2) whenever you reach a multiple of 3, print “fizz.”
3) whenever you reach a multiple of 5, print “buzz.”
4) if you reach a number that is a multiple of both 3 and 5, you should print both “fizz” and “buzz.”
If you are into the test-driven development/iteration paradigm, your progress might go something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// meets first criteria (print out 1 – 100) | |
for (x = 1; x <= 100; x++) { | |
console.log(x); | |
} | |
// fails to swap in fizz for multiples of 3 | |
// fails to swap in buzz for multiples of 5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (x = 1; x <= 100; x++) { | |
if (x % 3 == 0) { console.log("Fizz") } | |
if (x % 5 == 0) { console.log("Buzz") } | |
console.log(x); | |
} |
That came pretty close, but for a number that is a multiple of both 3 and 5, it prints fizz and buzz on two adjacent lines instead of a single line. Let’s get them on the same line if we can.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (x = 1; x <= 100; x++) { | |
if ( (x % 3 == 0) && (x % 5 == 0) ) | |
{ console.log("FizzBuzz!"); } | |
else if (x % 3 == 0) | |
{ console.log("Fizz"); } | |
else if (x % 5 == 0) | |
{ console.log("Buzz"); } | |
else | |
{ console.log(x); } | |
} |
At this point, we’ve met all the criteria and the script should be left alone. Except we both know there’s always a developer out there who has to make a “more elegant solution” . . . right?
Easy is the descent into Hell, for it is paved with good intentions.
– John Milton, Paradise Lost
It usually goes something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (x = 1; x <= 100; x++) { | |
var sayThis = ""; | |
if (x % 3 == 0) { sayThis += "Fizz"; } | |
if (x % 5 == 0) { sayThis += "Buzz"; } | |
if (sayThis == "") { sayThis = x; } | |
console.log(sayThis); | |
} |
Shiny! By using a variable as a flag, we can get rid of those ugly chained if-else statements. See what an elegant programmer I am? #snark
It doesn’t end there, naturally. Someone else comes along and says “Hey, if we make this into a function with parameters, then we can solve this puzzle game using any starting and finishing numbers!” (And I’ll admit it– I’ve been THAT guy before.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function fizzbuzz(start, finish) { | |
for (x = start; x <= finish; x++) { | |
var sayThis = ""; | |
if (x % 3 == 0) { sayThis += "Fizz"; } | |
if (x % 5 == 0) { sayThis += "Buzz"; } | |
if (sayThis == "") { sayThis = x; } | |
console.log(sayThis); | |
} | |
} |
So now we can do things like this:
fizzbuzz(1, 100000);
fizzbuzz(-1000, 1000);
Isn’t that incredibly useful? #moreSnark
But what if we want to play a version of the game that uses different numbers than 3 and 5, or we want to play the game in a foreign language? I know, we can pass even more parameters through the function call!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function fizzbuzz(start, finish, modFizz, modBuzz, txtFizz, txtBuzz) { | |
for (x = start; x <= finish; x++) { | |
var sayThis = ""; | |
if (x % modFizz == 0) { sayThis += txtFizz; } | |
if (x % modBuzz == 0) { sayThis += txtBuzz; } | |
if (sayThis == "") { sayThis = x; } | |
console.log(sayThis); | |
} | |
} |
There! That ought to handle anything we should need.
fizzbuzz(-1000, 1000, 2, 13, “Gallifrey Falls”, “No More”);
Except with all those required parameters, it’s only a matter of time until someone accidentally omits one (which will throw an “undefined” error) or enters them in the wrong sequence (in which case the code will run but likely return unintended results). Yikes!
We can add a verbose documentation header to our function . . .
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* fizzbuzz() function | |
* I play the fizzbuzz game (http://en.wikipedia.org/wiki/Fizz_buzz) | |
* | |
* arguments: | |
* start – required integer (e.g. 1) | |
* finish – required integer (e.g. 100) | |
* modFizz – required integer (e.g. 3) | |
* modBuzz – required integer (e.g. 5) | |
* txtFixx – required string (e.g. "fizz") | |
* txtBuzz – required string (e.g. "buzz") | |
* | |
* Usage: fizzbuzz(1,100,3,5,"fizz","buzz"); | |
*/ | |
function fizzbuzz(start, finish, modFizz, modBuzz, txtFizz, txtBuzz) { | |
for (x = start; x <= finish; x++) { | |
var sayThis = ""; | |
if (x % modFizz == 0) { sayThis += txtFizz; } | |
if (x % modBuzz == 0) { sayThis += txtBuzz; } | |
if (sayThis == "") { sayThis = x; } | |
console.log(sayThis); | |
} | |
} |
but no one reads directions any more, do they? #yesEvenMoreSnark
And this is when the Devil himself walks in and says, “Have you considered making it . . . object oriented?”
YES, OF COURSE! WHY DIDN’T I THINK OF THAT BEFORE? EVERYONE KNOWS THAT OBJECTS FIX EVERYTHING! (And we can even initialize our properties with default values that users can override as needed.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* fizzbuzz() object | |
* I play the fizzbuzz game (http://en.wikipedia.org/wiki/Fizz_buzz) | |
* | |
* properties: | |
* start – integer (optional, defaults to 1) | |
* finish – integer (optional, defaults to 100) | |
* modFizz – integer (optional, defaults to 3) | |
* modBuzz – integer (optional, defaults to 5) | |
* txtFixx – string (optional, defaults to "fizz") | |
* txtBuzz – string (optional, defaults to "buzz") | |
* | |
* methods: | |
* play(); | |
* | |
* Usage: | |
* example 1: | |
* fizzbuzz.play(); // uses default values | |
* | |
* example 2: | |
* fizzbuzz.start = 5; | |
* fizzbuzz.finish = 50; | |
* fizzbuzz.play(); // plays with 5 – 50, uses default values for other properties | |
*/ | |
var fizzbuzz = { | |
start: 1, | |
finish: 100, | |
modFizz: 3, | |
modBuzz: 5, | |
txtFizz: "fizz", | |
txtBuzz: "buzz", | |
play: function(){ | |
for (x = this.start; x <= this.finish; x++) { | |
var sayThis = ""; | |
if (x % this.modFizz == 0) { sayThis += this.txtFizz; } | |
if (x % this.modBuzz == 0) { sayThis += this.txtBuzz; } | |
if (sayThis == "") { sayThis = x; } | |
console.log(sayThis); | |
} | |
} | |
} |
And that, Ladies and Gentlemen, is how to transform a simple for loop into a JavaScript object with more documentation than actual code.