DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,503 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for The Most Convoluted JavaScript Fizz Buzz Solution
Dale L. Jefferson
Dale L. Jefferson

Posted on • Updated on • Originally published at dalejefferson.com

The Most Convoluted JavaScript Fizz Buzz Solution

Finding the correct level of decomposition is sometimes challenging. In this article, I will take decomposition to the extreme creating a wildly convoluted Fizz Buzz solution and share my feelings on the correct level of decomposition.

Fizz Buzz Test

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”.

const modulo = n => a => a % n;
const equals = a => b => a === b;
const compose = (...a) => x => a.reduceRight((p, fn) => fn(p), x);
const equalsZero = equals(0);
const isDivisibleBy = a =>
  compose(
    equalsZero,
    modulo(a)
  );
const isDivisibleBy3 = isDivisibleBy(3);
const isDivisibleBy5 = isDivisibleBy(5);
const isDivisibleBy15 = isDivisibleBy(15);
const ifElse = (condition, onTrue, onFalse) => n => {
  return condition(n) ? onTrue(n) : onFalse(n);
};
const identity = x => x;
const always = value => () => value;

const fizzBuzz = ifElse(
  isDivisibleBy15,
  always("FizzBuzz"),
  ifElse(
    isDivisibleBy3,
    always("Fizz"),
    ifElse(isDivisibleBy5, always("Buzz"), identity)
  )
);

for (let index = 1; index <= 100; index++) {
  console.log(fizzBuzz(index)); // 1, 2, Fizz, 4, Buzz
}

I would hope it is obvious to the reader that I have taken decomposition too far, the question is when should I have stopped and how would I know to stop.

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. - Antoine de Saint-Exupery

Let's take this step by step.

Starting Point

const fizzBuzz = n => {
  if (n % 15 === 0) {
    return "FizzBuzz";
  } else if (n % 3 === 0) {
    return "Fizz";
  } else if (n % 5 === 0) {
    return "Buzz";
  } else {
    return n;
  }
};

Pretty concise code, we could make it a little easier to read and remove duplicated logic by extracting the divisible check into its own function.

First Refactor

const isDivisibleBy = (a, n) => n % a === 0;

const fizzBuzz = n => {
  if (isDivisibleBy(15, n)) {
    return "FizzBuzz";
  } else if (isDivisibleBy(3, n)) {
    return "Fizz";
  } else if (isDivisibleBy(5, n)) {
    return "Buzz";
  } else {
    return n;
  }
};

Now we have the reusable isDivisibleBy function, but should we break it down further? Is isDivisibleBy doing two things finding the remainder and a comparison?

Second Refactor

Extract till you Drop. - Uncle Bob

One Thing: Extract till you Drop.

const equalsZero = n => n === 0;
const modulo = (a, n) => n % a;
const isDivisibleBy = (a, n) => equalsZero(modulo(a, n));

const fizzBuzz = n => {
  if (isDivisibleBy(15, n)) {
    return "FizzBuzz";
  } else if (isDivisibleBy(3, n)) {
    return "Fizz";
  } else if (isDivisibleBy(5, n)) {
    return "Buzz";
  } else {
    return n;
  }
};

Should we stop now or push on? How many of you have "dropped"?

I know it when I see it

I shall not today attempt further to define the kinds of material I understand to be embraced within that shorthand description ["hard-core pornography"], and perhaps I could never succeed in intelligibly doing so. But I know it when I see it, and the motion picture involved in this case is not that. - Justice Potter Stewart

Personally, when I get to the point that the refactored code becomes more complex and harder to read than the original I stop or revert the refactor. I believe this happened in this simplified example at the second refactor. Like many things in software development, this is a personal preference, all versions of this code work as well as all the others, actually, I would imagine the starting point code is the fastest.

Let me know what you think in the comments, how far do you take decomposition?

Top comments (0)

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

β­οΈπŸŽ€ JavaScript Visualized: Promises & Async/Await

async await