Beginner’s FizzBuzz Kata Walkthrough in JavaScript using Test Driven Development (TDD)

Lucy Ironmonger
29 min readDec 5, 2021

--

This is a great beginner’s kata if you are just starting out in JavaScript. Or, perhaps you’re looking to get yourself warmed up for some tech tests; if so, this is a classic interview question. We will be using Test Driven Development (TDD), which is again a great thing to display in interview.

There are 2 slightly different FizzBuzzes — one which produces an array of numbers up to a chosen number, and one where you input a number and it confirms if it’s just a number, “Fizz”, “Buzz”, or “FizzBuzz”.

This post will go through the first in detail. Learn with me as I refactor and explore TDD, and if you have any suggestions or improvements, please feel free to comment. If you enjoy this post, you can give up to 50 claps 👏 which will help others find it ☺️ a follow will also help the algorithm, and loop you into more content I write.

What is the FizzBuzz kata?

Print integers (numbers) 1 to N (e.g. N could be… 100)
Print “Fizz” if an integer is divisible by 3
Print “Buzz” if an integer is divisible by 5
And print “FizzBuzz” if an integer is divisible by both 3 and 5.

This would go something like, given the number 15, it would return:
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz.

A note that 15 would be FizzBuzz, as it divides by both 3 and 5.

Step 1: Acceptance Criteria & Questioning

Before we jump into the code, let’s think carefully about the acceptance criteria, as this will form the starting point for your tests.

Take as many clues from the kata as possible.

Does it raise any questions we need to clarify?

Print integers 1 to N

N is not specified in the kata, therefore it will be an argument that we pass into the function. It could be any number…

  • Therefore — something to clarify, is N always a positive number? Would there be a case where N is a minus number? Worth asking the interviewer at this stage as this will affect the tests we write.

It’s asking us to print numbers 1 to N so I’m thinking a list — eg, an array might come in handy.

It’s also a list of numbers forming a predictable pattern (eg +1 each time) — so a loop would be a good thing to explore for this solution.

Print “Fizz” if an integer is divisible by 3

Good to know — we will need to know how to confirm if a number is fully divisible by 3. More on that later (hint: modulo!)

Print “Buzz” if an integer is divisible by 5

Notice how similar this is to the previous requirement — maybe we can reuse some logic?

And “FizzBuzz” if an integer is divisible by both 3 and 5.

Do you notice the pattern that we are asked to add “Buzz” onto “Fizz”? Convenient that it’s not asking for say, the word “Lemonade” if it’s divisible by 3 and 5. That it’s asking for “FizzBuzz” alludes to a potential approach — take note that it is concatenating the words given by previous acceptance criteria.

Step 2: Let’s think about the tests

Kent Beck said it best in ‘Test-Driven Development By Example’:

Imagine programming as turning a crank to pull a bucket of water from a well. When the bucket is small, a free-spinning crank is fine. When the bucket is big and full of water, you’re going to get tired before the bucket is all the way up. You need a ratchet mechanism to enable you to rest between bouts of cranking. The heavier the bucket, the closer the teeth need to be on the ratchet.

The tests in test-driven development are the teeth of the ratchet. Once you get one test working, you know it is working, now and forever. You are one step closer to having everything working than you were when the test was broken. Now get the next one working, and the next, and the next.

By analogy, the tougher the programming problem, the less ground should be covered by each test.

I wanted to quote this because I feel we’ve got some good clues on where to start. For that reason, I’m not necessarily going to zero in as much as I could with the tests, but we’ll make sure we’ve got the main big hitters in.

Let’s start writing out some ideas for ‘happy path’ testing. This means, you’re assuming the right inputs are given to the function — eg we’re not putting in minus numbers, or no numbers, or strings — we are expecting to put in the sort of inputs we want in order to run the tests successfully — eg, positive whole numbers which are bigger than 1.

So let’s start small, with the basics, then build — let’s not worry about “Fizz” and “Buzz” straight away, the first thing is passing in N and getting a list back.

When given a number, N, it returns an array from 1 to the number N.
It would be worth thinking perhaps we just initially pass the number ‘2’, and expect an array of [1, 2]

Now we can start thinking about “Fizz” etc.

If the number in the array is divisible by 3, print “Fizz”
Then we can build on this, passing say, ‘3’, and would expect [1, 2, “Fizz”]

If the number in the array is divisible by 5, print “Buzz”
Now we could pass 5, and would expect [1, 2, “Fizz”, 4, “Buzz”]

And finally —

If the number in the array is divisible by 3 and 5, print “FizzBuzz”
A way to work out the first number that is divisible by 3 and 5 is to actually times 3 x 5 = 15. If we passed in 15 to our FizzBuzz we would expect:
[1, 2, “Fizz”, 4, “Buzz”, “Fizz”, 7, 8, “Fizz”, “Buzz”, 11, “Fizz”, 13, 14, “FizzBuzz”]. However we want to probably go one beyond this, and test 30 as well. More on that later.

A big tip for junior devs

Juniors, if you’re in a tech test interview, it doesn’t often really matter how far through the actual code you get. What they’re often much more interested in than your code, is how you communicate, and how you think.

This is where pseudo-code comes in. Pseudo-code means basically writing out in plain English (or your native language) how you would approach the kata, before then adapting that into code — so all the above tests we’ve outlined are a helpful structure for that (notice we haven’t written any code yet!)

If you solved the FizzBuzz kata with really nice code but kept quiet whilst doing it as you were concentrating, that could go actually go against you. Someone that didn’t finish the code for the kata, but talked openly about what they were thinking about whilst writing out pseudo-code, may well come off better in an interview. Transparency is important, so just say what you’re thinking, even if you’re not sure of the answers — it’s better than staying quiet. The answers will likely start to come to you the more you talk it out.

Pseudo-code is also a nice way to settle into a tech test, especially if you’re feeling under pressure, and a bit fingers and thumbs with your code. You can get settled by chatting, before piecing the code together.

Step 3: Setup your JavaScript project & Jest test from scratch.

If you’re not sure how to do this, I go through it from the very beginning assuming no prior knowledge, running through some really helpful command line tricks.

Follow that guide here.

Once you’ve done that — onwards!

Step 4: Let’s write the first TDD Test

When given a number, N, it returns an array from 1 to the number N.

I would say it’s really important to demonstrate TDD if you’re in a tech test, and be able to talk about the reasons why you’re doing it. There are many reasons, but here are some main ones:

  • TDD forces you to not overthink — to take the problem one step at a time, even if you feel like you want to rush headlong into it.
  • If you’re not sure how to approach the problem, TDD helps you take baby steps into it.
  • It also re-assures you as you build up your code, that the new code you’re adding isn’t breaking things unintentionally.

Here’s how to approach every TDD test, and make sure to talk about this in interview:

🔴 Red — write a test that fails (we haven’t written any code for it to pass yet).
🟢 Green — then write code to make the test pass, write it fast, it can be messy.
♻️ Refactor — tidy up the code you’ve written, and make sure it’s still passing.

Then sit back and enjoy the green ticks of the passing tests. ✅ Ahhhh. 😎

You might be wondering, why on earth write a failing test first (the red stage)? Well, imagine if you accidentally wrote a test that was giving a false positive (eg, a passing test) despite not having the correct solution code — or any code! The test would be returning a positive test result (green tick) when it shouldn’t, even when your code wasn’t necessarily correct, or even in existence. It’s therefore not a good test. Remember, your test is there to give you surety your code is correct.

By proving the test has moved from being broken (red), to operational (green) by virtue of adding the solution code in, it proves the test is working as it should, and also that the solution code is on the right lines if it’s causing the test to pass.

In our test file, we will always aim to write out the test first, before writing our code. This is why it’s test-driven. This will start with being a ‘red’ stage test.

In our test file, let’s code:

// fizzbuzz.test.jsconst fizzBuzz = require("../src/fizzbuzz")describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
// this is where we'll write our test}}

If you take a close look at the above, you can see how it’s written out as a series of callback arrow functions. Have a go at typing this out for yourself to get the hang of the structure, it’s easier than it looks.

Now let’s explore how we’d write the test within it.

Step 5: Arrange Act Assert

Arrange, Act, Assert is a nice approach when it comes to TDD. I tend to like setting up variables too for expected and actual — I think it keeps it quite readable. What do you think?

Being mindful of the first acceptance criteria (AC) we’ve drawn up, this is how A-A-A goes.

// fizzbuzz.test.js fileconst fizzBuzz = require("../src/fizzbuzz");describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
// ARRANGE THE SETUP FOR THE TEST
let N = 2;
let expected = [1, 2]
// ACT - CALL YOUR METHODS!
let actual = fizzBuzz(N);
// ASSERT WHAT YOU WANT TO HAPPEN
expect(actual).toEqual(expected);
}); // (slip in the next test here - more on that later)
});

Let’s just talk about this line:

expect(actual).toBe(expected)

What does this actually mean? Let’s read through broken down:

The output of fizzBuzz(2) (actually what we’re going to do) should be the same as what we expect — which is [1, 2] .

It’s a nice way to approach the test when you might be flapping — thinking ‘what am I going to do, and what do I want to happen as a result of that?’. I find A-A-A really helpful to get my bearings when writing tests.

Now that’s setup, it gives us somewhere to go with writing the code; we know what to expect from the outcome of the code. Hit save on what you’ve done in the test file.

Then without further ado, type npm test in your terminal, and you should find it throws an error when it runs the test. This is normal — it’s importing an empty fizzBuzz function. Congrats — you just ran the red stage of test 1! Now let’s get to green :)

Step 6: Let’s write the first piece of code to paint the town green

From VS Code, you should be able to navigate to and open the fizzbuzz.js file. Let’s draw up a basic fizzBuzz function if you haven’t already got one:

// fizzbuzz.js file const fizzBuzz = () => {// our function will go in here}module.exports = fizzBuzz

Remember, our test has helped us get started — nice one TDD!:

When given a number, N, it returns an array from 1 to the number N

So, we know we’re passing in N:

const fizzBuzz = (N) => {// our function will go in here}module.exports = fizzBuzz

We know we want to loop through the numbers 1 at a time, going up. A for loop seems like a good weapon of choice. We have all the components we need for it:

  • We want it to start at 1, so we can write i = 1
  • N will be the last number it will stop on, so we can write i ≤ N
  • We want to go up 1 number at a time, so we can write i++
const fizzBuzz = (N) => {for (i = 1; i <= N; i++) {
console.log(i)
}
}
fizzBuzz(2)module.exports = fizzBuzz

To get feedback on what this code is doing, I can do several things:

  • Run the tests running npm test in the terminal when I’m in the root folder of the project — this will show me what the test expects in green, and what it receives in red.
  • I can console.log to see what the function brings back — notice I’m doing this inside the for loop and will see this printed in the terminal.
  • I can also invoke the function at the bottom of the file and pass it a number, as you can see on line 21 below fizzBuzz(2). I can run this by typing node fizzbuzz.js briefly — this runs the JavaScript file without running the test suite.

Have a go with these 3 and see what results you get.

Something important to note is that when info is returned to the console like this, it is a side-effect. The fizzBuzz function will currently evaluate to ‘undefined’ because we are not returning anything from the function yet

I can see my console.log is printing 1 and 2, so now I can go further and put it into an array.

The console shows the values 1 and 2, which have been logged from the function fizzBuzz.

Let’s change the code:

// fizzbuzz.jsconst fizzBuzz = (N) => {
let array = []
for (i = 1; i <= N; i++) {
array.push(i)
}
return array
}
module.exports = fizzBuzz
  • I’ve created an empty array outside of the loop let array = []. Too often I have accidentally created it inside the loop, which if we did would just mean the array gets reset back to [] every time the loop goes around.
  • I’ve changed the console.log(i) to array.push(i), which will add i each time the loop goes round, to the array. Remember, i is a number and will change each time — so i will start as 1, then 2, and so on, as we’re doing i++ each time the loop goes around.
  • Again outside the loop, I have added return array which will run once the loop is finished. This will return my lovely array which has had all the numbers pushed into it! And it should also stop us getting ‘undefined’ — yay!

Now we can run our first test again, with npm test. Boom! The test now passes. Green tick ahoy 👏

1 passing test.

Can we refactor? Have a think. At this stage, the only thing standing out is the excessive comments in the test file — so let’s remove them and tidy that up for the next test.

Step 7: Test 2 incoming

If the number in the array is divisible by 3, print “Fizz”

Cool, how are you feeling about writing this next test out? Have a go before looking at my approach below. You can add another ‘test’ section in the same describe block like this (just watch the brackets tho… ah.. brackets…)

// fizzbuzz.test.jsconst fizzBuzz = require("../src/fizzbuzz");describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
let N = 2;
let expected = [1, 2];
let actual = fizzBuzz(N);
expect(actual).toEqual(expected);
});test("If the number in the array is divisible by 3, print 'Fizz'", () => {// ARRANGE// ACT// ASSERT});
});

Cool — so this is how I’d give it a go with what we initially lined out:

const fizzBuzz = require("../src/fizzbuzz");describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
let N = 2;
let expected = [1, 2];
let actual = fizzBuzz(N);
expect(actual).toEqual(expected);
});test("If the number in the array is divisible by 3, print 'Fizz'", () => {// ARRANGE
let N = 3;
let expected = [1, 2, "Fizz"];
// ACT
let actual = fizzBuzz(N);
// ASSERT
expect(actual).toEqual(expected);
});
});

Run npm test, we should now have 1 passing test (test 1) , and 1 failing (the red stage of this test).

Now to the code. With coding, the answers often lie in the words we choose, and the questions we ask. So,

If the number in the array is divisible by 3, print “Fizz”

  • How can we confirm a number being passed in is divisible by 3?

Let’s have a look at some examples and do some basic maths:

10 / 3 = 3 times, with 1 remainder (not divisible)
9 / 3 = 3 times, with no remainder (divisible by 3!)
8 / 3 = 2 times, with 2 remainder (not divisible)
7 / 3 = 2 times, with 1 remainder (not divisible)
6 / 3 = 2 times, with no remainder (divisible by 3!)

So can you a see a pattern? The numbers which are divisible by 3, have no remainder when being divided by 3.

Therefore, we can make a hypothesis:

By proving a number has no remainder when dividing it by 3, it means we’ve found a number that is truly divisible by 3.

How would we write this in code? Well, there’s an operator which is perfect for establishing if there is a remainder or not. This operator is modulo which has a lovely % sign.

What modulo does is it tells you the remainder left after dividing a number. Let’s take the same examples as above:

10 % 3 = 1

So the way I read this statement is whenever I see the percentage sign, my brain reads it as ‘10 divided by [number after the % sign, eg, 3] leaves a remainder of *modulo!* (said in abracadabra style).

10 % 3 = 1 … aka “10 divided by 3 leaves a remainder of… modulo, 1!”

How to work out the modulo in code

What I tend to do is bash in a little console.log:

A line at the bottom of the code saying console.log(10 % 3);

Then run node fizzbuzz.js to run your code file. It should return this in the console:

The console shows the value 1.

Let’s single out the numbers that only divide by 3 with no remainder using our friend modulo. Why? Because we want to make them all turn into “Fizz”!

Let’s write in pseudo-code first:

// if the number when divided by 3 has 0 remainder… return Fizz

Now let’s do the code! Remember === means ‘strictly equal to’.

if(N % 3 === 0) {
return “Fizz”
}

Let’s connect the logic so far 🧠

  • Numbers divided by 3 which have 0 remainder are fully divisible by 3 ✅
  • Modulo is a great way for us to check if a number has a remainder ✅
  • if (number % 3 === 0) will root out those numbers divisible by 3 that have no remainder 📣

Let’s put it altogether now with our code. What I’ve done here is within the loop, basically say, check if this number i is first of all divisible by 3 — if it is, push “Fizz” to the array. If it’s not, move onto the ‘else’ — which is to push the number (i).

// fizzbuzz.js const fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {if (i % 3 === 0) {
array.push("Fizz");
} else {

array.push(i);
}
}
return array;
};
module.exports = fizzBuzz

!important point on the Scope of Tests

Notice I put array.push(“Fizz”) and not return “Fizz”. This is because return would end the function prematurely, when it hits that return.

Think about the implications of this. If I had put return “Fizz” and set the test with N as 3, this would mean the test would pass — it would print [1, 2, “Fizz”]. However if I put N as 6, the test would fail, as it would only print [1, 2, “Fizz”] — when it hits the 3, it returns and exits the function.

What can we take from this? Make sure you have tests that are wide-ranging, and not too narrow in scope.

On this advice, I know that my final test is going to run a range up to 30. Assuming I had put return “Fizz” in my code unassumingly, tests 1, 2 and 3 would pass. But when I then run the final test, expecting an array of up to 30 items — test 4 would fail — only returning [1, 2, “Fizz”]. This would then help me spot the error. This is why we love tests. 🎉

As a side-line — if you fancy it, give this a go and have a play with:

  • Putting this code in instead:
// fizzbuzz.js const fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {if (i % 3 === 0) {
return "Fizz"
} else {

array.push(i);
}
}
return array;
};
module.exports = fizzBuzz
  • Running the test with it passing in N as 3, and expecting [1, 2, “Fizz”] — the test should pass.

Now have a think about the alternative:

  • Imagine if you run the same test but with N as 6 — what would you set your test to expect? [1, 2, “Fizz”, 4, 5, “Fizz”]?

With the error of the ‘return Fizz’ in our code, the test with N as 6 would fail, and flag up the code error of ‘return Fizz’ as it wouldn’t get to 6, it only get to [1, 2, “Fizz”] before the loop ends.

Imagine you kept in the N as 6 test, and fixed your code so the test passed [1, 2, “Fizz”, 4, 5, “Fizz”].

With the next bit of code you implement, it would replace divisibles of 5 with “Buzz”. Our N as 6 test would then go from passing, to then failing ❌ as this test would then start receiving [1, 2, “Fizz”, 4, “Buzz”, “Fizz”], but be expecting [1, 2, “Fizz”, 4, 5, “Fizz”].

An important point to make here is that you don’t want to go back and re-write tests. Tests should ideally test 1 thing.

But sometimes it’s hard to isolate just 1 thing — this is why FizzBuzz is a cool kata to explore, because it exposes that — and this may be a talking point in an interview.

So let’s recap on what our tests are proving:

Test 1: An array of numbers incrementing by 1, from 1-N is produced — testing [1, 2] with N as 2 — 👇 not ideal, would like to have tested a wider range — but I’m wary that any more tests will break as we start introducing Fizzes and Buzzes. ✅ But 1, 2 does show it’s returning an array, and the number is incrementing up to N.

Test 2: Divisibles of 3 print “Fizz” with N as 3 — testing [1, 2, “Fizz”] — again, can’t go wider at this point without running into “Buzz” problems as we haven’t yet proven this.

Test 3: Divisibles of 5 print “Buzz” — at this point we have now proved that divisibles of 3 print “Fizz”, so it’s OK to have “Fizz” in the results set now — testing [1, 2, “Fizz”, 4, “Buzz”, “Fizz”, 7, 8, “Fizz”, “Buzz”]. We don’t have more in the scope of the kata e.g. there’s no divisibles of 7 that should be printing “Mongoose”, so we’re OK to have a longer leash of scope with this test. This test could actually go right up to N as 14 — but we do have to still keep in mind we have one last curveball with “FizzBuzz” which will apply to number 15.

Test 4: Finally, we test that divisibles of 3 and 5 produce “FizzBuzz”. At this point we can encompass points 1, 2 and 3 in the testing as we have proven them — so this should be the test which is long-ranging and wider in scope (eg, we could go to 30 or more if we wanted to). This would ultimately catch the errors that previous tests might not have shown — eg, the return “Fizz” error.

If you’ve got thoughts on this or a different way you would approach this, I would really love to hear it. Blogging helps me learn and solidify my own practice, and I am very up for learning if there is a better way to approach this 😄 so please comment constructively if you think there is!

Cool, good food for thought hopefully! So, with test 2 as N as 3, and ensuring you have the code as array.push(“Fizz”) — you can save your work and run npm test. You should find you get another passing test! Well done, now we’ve done Red-Green… just time for the refactor, if it’s possible. At this stage, again, it’s looking not bad. We’ll move on and likely refactor after the 3rd test.

2 passing tests.

Step 8: Riff off the same idea, for “Buzz”

If the number in the array is divisible by 5, print “Buzz”

We already spotted that this is very similar to the previous test, but for “Buzz” and divisibles of 5. Imitation is the greatest form of flattery, so without further ado… (and let’s make N a little bigger this time, learning from the last test and the return “Fizz” saga — as mentioned this could go up to 14. We’ll stick with 10, because, reasons).

... test code from above...test("If the number in the array is divisible by 5, print 'Buzz'", () => {// ARRANGE 
let N = 10;
let expected = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"];
// ACT
let actual = fizzBuzz(N);
// ASSERT
expect(actual).toBe(expected);
});
});

Running npm test, the terminal is telling us it expected “Buzz”, what it actually got was 5, and 10. Which is good, because we’re in the Red phase.

The test shows it expected “Buzz” but got the numbers 5 and 10.

So for our code, we’ve got working logic we can re-use from “Fizz” only this time:

// if the number when divided by 5 has no remainder… return Fizz

if(N % 5 === 0) {
return “Buzz”
}

Green Phase in more detail

When you write code, especially in the Green phase — it can be as messy and long-winded as you like — the goal is to get to a solution fast. Don’t overthink it. And don’t worry about refactoring it, as tempting as it is — that’s for the refactor stage. Yep, DRY (Don’t Repeat Yourself) can go out the window if you want to! 😮

So if this was me, banging in the code, I’d do this to make the test pass:

const fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {if (i % 3 === 0) {
array.push("Fizz");
} else if (i % 5 === 0) {
array.push("Fizz");
}
else {
array.push(i);
}
}
return array;
};
module.exports = fizzBuzz

Ok! npm test should now give us 3 passing tests — woo!

Refactor in more detail

Now we’re in a better position to refactor. I’m ready to sack off some brackets first of all — we can remove { } if we put the code in-line with the condition — this means it’s no longer a code block so doesn’t need {}.

Remember to re-run npm test to check you’ve not fudged any code when refactoring. Oooh doesn’t this look nicer already?!

3 passing tests.

Right amigos, we’re gonna move onto the final test. This is the juicy one you’ve all been waiting for!

Step 9: The final countdown (err, test!)

If the number in the array is divisible by 3 and 5, print “FizzBuzz”

This is the fun one for writing out expected values, I’ll save you the trouble:

// fizzbuzz.test.jstest("If the number in the array is divisible by 3 and 5, print 'FizzBuzz'", () => {// ARRANGE
let N = 30;
let expected = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "Fizz", 22, 23, "Fizz", "Buzz", 26, "Fizz", 28, 29, "FizzBuzz"]
// ACT
let actual = fizzBuzz(N);
// ASSERT
expect(actual).toEqual(expected);
});
});

Now this is where it gets interesting. So the first thing you might do, I certainly did when I was first learning, was this — a line that basically says, if it’s divisible by 3, and divisible by 5, push ‘FizzBuzz’.

// fizzbuzz.jsconst fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else if (i % 3 === 0 && i % 5 === 0) array.push("FizzBuzz")
else array
.push(i);
}
return array;
};module.exports = fizzBuzz;

This doesn’t work. If we passed in 15 to this function, JavaScript will hit the if (i % 3 === 0) array.push(“Fizz”) first, and go ‘oh yes, 15 is divisible by 3 so we’ll print “Fizz”.

Let’s switch it up, and put our new statement at the top:

// fizzbuzz.jsconst fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {if (i % 3 === 0 && i % 5 === 0) array.push("FizzBuzz")
else if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else array
.push(i);
}
return array;
};module.exports = fizzBuzz;

This will now work when running npm test — because it’s now checking if N is divisible by 15 first, then 3, then 5. We’re now ready for a few refactor options, as you can see there is a lot of repetition.

4 passing tests.

Refactoring — juicy stuff

Honestly, the first thing that jumps out is, why not take out the && operator and change it to 15?

// fizzbuzz.jsconst fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {if (i % 15 === 0) array.push("FizzBuzz")
else if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else array
.push(i);
}
return array;
};module.exports = fizzBuzz;

This could be a totally respectable solution in a tech test. However, there are many ways to skin a cat. Are you ready to explore a bit further? Remember that Fizz and Buzz can concatenate to form FizzBuzz is no mistake; we can use this as a clue. So here’s one suggestion that I got from this excellent video by Tom Scott. Major props for this!

So how would we go about exploring our concatenation clue?

  • This little badger right here springs to mind += which basically means it takes the original value of the variable on the left, and adds to it the value of whatever is on the right. That is what you would do with FizzBuzz, for example:
let a = "Fizz";
let b = "Buzz";
let c = (a += b);
console.log(c) // will give you "FizzBuzz"

So how do we take that thinking further? Well, I think what we’re exploring is we want to sack off the if (i % 15 === 0) array.push(“FizzBuzz”) line. For if nothing else, it feels like we can use concatenation to add “Buzz” to “Fizz” rather than having to declare “FizzBuzz” outright. So where would that leave us?

Back at a previous version of our code.. hmm…

// fizzbuzz.jsconst fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else array
.push(i);
}
return array;
};module.exports = fizzBuzz;

Variables can make us more dynamic

So now from looking at this, I’m now thinking, ok, pushing “Fizz” to an array using array.push(“Fizz”) is very rigid. It’s final.

Variables can enable us to be more dynamic. Instead of pushing “Fizz” to an array, why don’t we push “Fizz” to a new variable.

A new variable that could stay ‘open’ for other additions, e.g. += “Buzz”.

Once the loop is done, we could then push this new variable to the array.

The new variable could then reset on the next go of the loop, to then be manipulated again, and pushed again. Etc. Etc.

Sounds ok doesn’t it? Let’s have a go…

  • Let’s create this new variable as an empty string “”, in the same way our array also is an empty array. Note this new variable is inside the loop for reasons mentioned above.
  • Let’s change array.push(“Fizz”) to now be newVariable += “Fizz”
  • In the same vein, let’s to the same with Buzz to be newVariable += “Buzz”
// fizzbuzz.jsconst fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {let newVariable = ""if (i % 3 === 0) newVariable += "Fizz";
else if (i % 5 === 0) newVariable += "Buzz";
else array
.push(i);
}
return array;
};module.exports = fizzBuzz;

So we’ve got our divisibles covered. By this theory, if the number 15 comes along, it will firstly satisfy if (i % 3 === 0) and add “Fizz” to newVariable. Then it will satisfy if (i % 5 === 0) and then add “Buzz” additionally to newVariable, therefore making newVariable equal to “FizzBuzz”. So that’s a good start!

What if it’s not divisible by 3 or 5, e.g. it’s a 7? I guess that’s the next bit of the logic we need to work on. For this, let’s take out else array.push(i) as this is becoming redundant fast — we won’t be pushing i to an array as we have implemented our newVariable to be more flexible and expandable. We will be pushing newVariable tp the array when all’s said and done.

So on that tip, what we actually need to be looking at is newVariable. If the loop gets past (i % 3 === 0) and (i % 5=== 0) and newVariable still remains untouched as “ ”, (eg if we pass it at 7), then it confirms to us that the number being passed in is not divisible by 3 or 5. This would mean that newVariable is simply i — the number in the loop. Once we’ve shaped newVariable into what it needs to be, newVariable is what we’re pushing into the array.

That’s the pseudocode — now let’s code it!

// fizzbuzz.jsconst fizzBuzz = (N) => {let array = [];for (i = 1; i <= N; i++) {let newVariable = ""if (i % 3 === 0) newVariable += "Fizz";
if (i % 5 === 0) newVariable += "Buzz";
if (newVariable === "") newVariable = i;
array.push(newVariable)
}
return array;
};module.exports = fizzBuzz;

This blew my mind the first time I did it, I really felt like I’d levelled up my understanding from the previous version of the code. You should definitely have a go at this yourself, eg, delete the code and rebuild this part til it makes sense.

So now to flip this on its head even more… are we ready?!

I was looking through the comments on Tom Scott’s video, and saw this comment from Alexis Mandelias:

Create a map (Object in js,) that correlates multiples (3, 5) to strings (“fizz”, “buzz”). Then for each number, go through every item in the map and if the key is a multiple, add the value to the string.

Oooooh I like the sound of that! Ready to try this one out?

Lookup Tables & Bracket Notation

So, I think what Alexis was getting at is that we could make an object, which has the keys as the numbers we are dividing by, and the values being the strings, known by some as a ‘Lookup table’.

const lookup = {
3: "Fizz",
5: "Buzz"
}

The next part then, how do we do that?

“Then for each number, go through every item in the map (our lookup table) and if the key is a multiple, add the value to the string”.

Let’s give that a go then… yes we will be doing a loop inside a loop, but it’s OK!

  • Our existing for loop will run through numbers 1 to N, going up in increments of i++.
  • Our new loop will then be in charge of looping through the lookup table keys, eg, it’ll check ‘3’ and ‘5’. We want it to loop the lookup table for every new value of i, because we want to check if there is ever a match between ‘i’ and the key in the lookup table. Therefore, the new loop needs to exist inside the for loop so it can run on each new value of ‘i’.
  • If there is a match between ‘i’ and the key in the lookup table, we can use the value of the lookup table on that match, to add to our newVariable.
// fizzbuzz.jsconst lookup = {
3: "Fizz",
5: "Buzz"
}
let array = [];for (i = 1; i <= N; i++) {let newVariable = ""for (let key in lookup) {
if (i % key === 0) { newVariable += lookup[key] }
}
if (newVariable === "") newVariable = i;
array.push(newVariable)

}
return array;
};module.exports = fizzBuzz;
  • We added our lovely lookup table, outside of the loop. Because of this, we can take out the hard-coded bits for if (i % 3 === 0) newVariable += “Fizz” and if (i % 5 === 0) newVariable += “Buzz” because we have essentially stored the values for what to print in the lookup table.
  • We kept in our newVariable — this is still what we will manipulate and push into the array so that doesn’t change.
  • We add in a for…in loop, to be used within the for loop.
  • We keep in our if (newVariable === “”) newVariable = i; check at the end, which won’t be handled by the lookup table.

For…In Loop

This looks complicated but it’s actually really simple and is a great one to have in the toolbox. Let’s walk through it together. Also if you get a sec, read the Mozilla docs.

for (let key in lookup)
This is basically saying “for each entry in the lookup object” — I have set the word as ‘key’ but it could be anything, ‘item’, ‘entry’ in the lookup— I just find ‘key’ helpful because it keeps it clear.

The key would be, for example, ‘3’, and the value of this would be “Fizz”. Another key would be ‘5’ and the value would be “Buzz”.

The for in loop will iterate through (aka go through one at a time) the keys in the lookup object, to see if there are any matches with the conditions we set. So it’ll check the 3, 5, and anything else we might add in the lookup table.

“for each key in the lookup”…

const lookup = {
3: "Fizz",
5: "Buzz"
}
for (let key in lookup)

then…if ‘i’ divided by the key leaves a remainder of 0 (remember our modulo fun earlier?) then we know that ‘i’ is fully divisible by the key. eg, if i is 5, it is fully divisible by the key 5, therefore we can then concatenate the value in the lookup table to the newVariable.

if (i % key === 0) { newVariable += lookup[key] }

So hang-fire, what’s this lookup[key]?

That is bracket notation, or what Mozilla calls ‘Property Accessors’ which actually makes a lot of sense. It’s the equivalent of dot notation, e.g. you could write lookup.3 (which would give “Fizz” or lookup.5 (which would give “Buzz”). But as the keys are being iterated over, e.g., it’s checking 3, it’s checking 5, it’s checking any others we add in — it’s easier to use a property accessor which directly accesses the values of those keys, and doesn’t really care what those keys are called. The way we do this is lookup[key] which will print the values, it’s the equivalent of lookup.3 (= “Fizz”) or lookup.5 (= “Buzz”).

Bracket notation is not something that will click immediately if you’re anything like me, but do have a play with it.

So what does all this mean?! Has it been a good refactor?

If we look back at our code, it’s now potentially a lot more extensible. Let’s say we wanted to add in Mongoose, to print for any numbers divisible by 2. If a number is divisible by 2, 3, and 5. we should get the number 30 printing “MongooseFizzBuzz”.

const lookup = {
2: "Mongoose",
3: "Fizz",
5: "Buzz"
}
let array = [];for (i = 1; i <= N; i++) {let newVariable = ""for (let key in lookup) {
if (i % key === 0) { newVariable += lookup[key] }
}
if (newVariable === "") newVariable = i;
array.push(newVariable)

}
return array;
};console.log(fizzBuzz(60));module.exports = fizzBuzz;

Let’s pop in a console.log(fizzBuzz(60)), hit node fizzbuzz.js and watch the world burn.

Adding in ‘Mongoose’ word for divisibles of 2 — and spotting ‘MongooseFizzBuzz’ for the numerical value 30.

The Goal of the Refactor

I’ll leave you with a passing thought from the comments on Tom Scott’s video, this one from ciknay —

What is the goal from your refactor achieving?

Have you got a goal in mind — is it to make it use less memory, or to make it more adaptable for future use?

It’s funny how programmers have different priorities in terms of what they see is important. You saw it more important to avoid repeating the magic number 5, however my immediate thought was to avoid adding string concatenation, as in most languages you’ve increased your memory overhead.

I hope you’ve enjoyed this post as much as I have writing it! If you have, give us a follow! You can also give up to 50 claps which will help others find it. 👏

Once again, please feel free to send any constructive feedback, every day’s a school day, or so it should be! I’ve tried to keep this as beginner friendly as possible but if there are parts that don’t make sense, please let me know and I’ll update accordingly.

Have a great day, and happy coding.

@lucyironmonger on twitter

--

--

Lucy Ironmonger

Software Developer documenting the small wins. Come say hi!