Earlier this year, I wrote about my approach to the FizzBuzz challenge. But I saw a video by Tom Scott last Friday which made me want to revisit the challenge. Let's step through my thought process!

Get it working

My first concern with any new project is to just get the damn thing working. I try—and often fail—not to optimise my code too early, since this often leads to over-engineering. Here's what I came up with on my first attempt:

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);
}
}

In my original post, I said:

"Although the variable name i is common within a for loop, I opted for number because it's more explicit in this case."

In hindsight, this was kind of stupid. Yes, single-letter variable names are bad for readability, but i is so common that it doesn't make sense to deviate.

It's also worth mentioning something else I said: the let statement ensures that the i variable (and message in the next section) is block-scoped to the loop.

Make it DRY

It's always a good idea to keep things DRY. In my opinion, the most obvious thing to fix is that we have four console.log() statements. Let's fix this by using a variable to store our message.

for (let i = 1; i <= 100; i++) {
let message;

if (i % 3 === 0 && i % 5 === 0) {
message = 'FizzBuzz';
} else if (i % 3 === 0) {
message = 'Fizz';
} else if (i % 5 === 0) {
message = 'Buzz';
} else {
message = i;
}

console.log(message);
}

For the sake of simplicity, it's arguably best to stop here. But I have a couple of further ideas, so let's continue just for fun.

Separate the message from the number

It occurred to me that it might make sense to keep the message variable separate from the i variable. This way, it makes it clear whether we're printing an actual message or just a number.

for (let i = 1; i <= 100; i++) {
let message;

if (i % 3 === 0 && i % 5 === 0) {
message = 'FizzBuzz';
} else if (i % 3 === 0) {
message = 'Fizz';
} else if (i % 5 === 0) {
message = 'Buzz';
}

console.log(message ?? i);
}

I removed the else clause, so if none of the conditions is truthy, the message variable never gets initialised. This means it remains undefined, which is falsy. I then used the nullish coalescing operator within my console.log() statement, which only returns its right-hand operand when its left-hand operand is null or undefined. In other words: if the message variable is set, log its value; otherwise, log the value of i.

Simplify the first condition

Instead of checking if i % 3 === 0 and i % 5 === 0, we can just check if i % 15 === 0. This is because 15 is the lowest common multiple of 3 and 5:

for (let i = 1; i <= 100; i++) {
let message;

if (i % 15 === 0) {
message = 'FizzBuzz';
} else if (i % 3 === 0) {
message = 'Fizz';
} else if (i % 5 === 0) {
message = 'Buzz';
}

console.log(message ?? i);
}

The headline for this section might be a bit misleading, because whether this really does simplify the condition is open to debate. It certainly makes it cleaner, but I think it makes the code less obvious, which is something I'd usually avoid.

Wrapping up

Which of these solutions do you like best? I think the second iteration is my favourite. I'd love to see your solutions as well, so please reach out if you'd like to share!