# FizzBuzz in JavaScript with infinite arrays and multimapping

Published on

Fun way to solve the simple puzzle. Infinitely repeating arrays and mapping over multiple arrays almost makes the remainder/modulo operator (`%`) unnecessary.

## Rules of FizzBuzz

A FizzBuzz sequence consists of numbers and strings where:

• every 3rd item is "Fizz"
• every 5th item is "Buzz"
• every 15th item is "FizzBuzz"
• and other items are numbers.

So the first 15 items are:

1. 1
2. 2
3. Fizz
4. 4
5. Buzz
6. Fizz
7. 7
8. 8
9. Fizz
10. Buzz
11. 11
12. Fizz
13. 13
14. 14
15. FizzBuzz

After that, the cycle repeats. (The 16th item is 16 instead of 1, and so on.)

## The challenge

Here's a typical solution, though without a `for` loop:

``````const result = Array.from({ length: 15 }, (_, i) => {
const n = i + 1
if (n % 15 === 0) return 'FizzBuzz'
if (n % 3 === 0) return 'Fizz'
if (n % 5 === 0) return 'Buzz'
return n
})

console.log(result)
// [
//        1,      2, 'Fizz',      4,     'Buzz',
//   'Fizz',      7,      8, 'Fizz',     'Buzz',
//       11, 'Fizz',     13,     14, 'FizzBuzz',
// ]
``````

It works a-okay, but let's solve FizzBuzz without using the remainder operator in the usual way, i.e. without these:

• `if (n % 15 === 0)`
• `if (n % 3 === 0)`
• `if (n % 5 === 0)`

Before that, we need to acquire two new skills: creating infinite arrays and mapping over multiple arrays.

## Infinite arrays

How arrays normally work (d'oh):

``````const array = ['A', 'B', 'C']

array.length //=> 3

array[0] //=> 'A'
array[1] //=> 'B'
array[2] //=> 'C'
array[3] //=> undefined
array[4] //=> undefined
// ...
``````

What we want: when accessing an out-of-bounds numeric property of the `array` object, cycle back to the beginning of the array.

That can be achieved by creating a `Proxy`:

``````const proxyHandler = {
get(target, prop) {
// Accessing `target.length`
if (prop === 'length') {
return Infinity
}

// Accessing a numeric property of `target`, e.g. `target[5]`
if (typeof prop === 'string' && /^\d+\$/.test(prop)) {
return target[prop % target.length]
}

// Accessing whatever else, e.g. `target.foo`
return target[prop]
},
}

const cycle = (array) => new Proxy(array, proxyHandler)
``````

I named the function `cycle` because I was inspired by Clojure's `cycle` function.

Usage:

``````const infiniteArray = cycle(['A', 'B', 'C'])

infiniteArray.length //=> Infinity

infiniteArray[0] //=> 'A'
infiniteArray[1] //=> 'B'
infiniteArray[2] //=> 'C'
infiniteArray[3] //=> 'A'
infiniteArray[4] //=> 'B'
infiniteArray[5] //=> 'C'
infiniteArray[6] //=> 'A'
// ...
``````

### Notes about the `typeof prop` check

#### Why is the `typeof prop === 'string'` check in the proxy handler's `get` method needed?

Otherwise the regex test would throw an error if `prop` was a Symbol.

For example, if `prop` was `Symbol.iterator`:

``````;/^\d+\$/.test(Symbol.iterator)
//=> TypeError: Cannot convert a Symbol value to a string
``````

The error is thrown because `RegExp.prototype.test` expects a string argument, and Symbols can't be implicitly converted to strings.

`Symbol.iterator` is used by `for...of` loops. So, using `infiniteArray` in a `for...of` loop would thus throw an error without the `typeof` check.

Using any other Symbol property key would throw as well without the `typeof` check.

#### Why check that `typeof prop` is `'string'` instead of `'number'`?

E.g. in `infiniteArray[5]` the property key is a number, right? Well yes, but actually no.

Numeric property keys are converted to strings. From Working with objects on MDN:

All keys in the square bracket notation are converted to string unless they're Symbols, since JavaScript object property names (keys) can only be strings or Symbols.

#### An alternative to the `typeof prop` check

I said above that Symbols can't be implicitly converted to strings. But a Symbol can be converted explicitly to a string by calling its `toString()` method. Doing so would make the `typeof prop` check unnecessary:

``````-if (typeof prop === 'string' && /^\d+\$/.test(prop)) {
+if (/^\d+\$/.test(prop.toString())) {
return target[prop % target.length]
}
``````

Neither option is very clear without an explanation. I might prefer the `toString()` alternative.

### Shortcomings

The `cycle` function is imperfect. For example, slicing doesn't work:

``````const infiniteArray = cycle(['A', 'B', 'C'])

// Current behavior:
infiniteArray.slice(0, 7)
//=> ['A', 'B', 'C', empty × 4]

// Ideal behavior:
infiniteArray.slice(0, 7)
//=> ['A', 'B', 'C', 'A', 'B', 'C', 'A']
``````

Supporting all array methods would be interesting. I'll leave it to you as an exercise.

The current imperfect implementation is enough for solving FizzBuzz, so let's proceed.

## Multimapping

Clojure's `map` function is similar to JavaScript arrays' `map` method, except in Clojure you can map over multiple arrays at once:

`map` returns a lazy sequence consisting of the result of applying [the function] `f` to the set of first items of each `coll`, followed by applying `f` to the set of second items in each `coll`, until any one of the `coll`s is exhausted. Any remaining items in other `coll`s are ignored.

A `multimap` function can be implemented in a few lines of JavaScript (I'm ignoring the part about returning a lazy sequence):

``````const multimap = (fn, ...arrays) => {
const arrayLengths = arrays.map((array) => array.length)
const smallestArrayLength = Math.min(...arrayLengths)

return Array.from({ length: smallestArrayLength }).map((_, i) => {
const items = arrays.map((array) => array[i])
return fn(items, i)
})
}
``````

Usage:

``````// Same lenghts -> loop over 4 times
multimap(
(args, i) => {
console.log(i, args)
return i
},
['A', 'B', 'C', 'D'],
['W', 'X', 'Y', 'Z']
)
// Console loggings:
//   0, ['A', 'W']
//   1, ['B', 'X']
//   2, ['C', 'Y']
//   3, ['D', 'Z']
// Return value:
//   [0, 1, 2, 3]

// Different lengths -> loop over 3 times (the length of the shortest array)
multimap(
(args, i) => {
console.log(i, args)
return args[i]
},
['♠', '♡', '♢', '♣'],
['A', 'B', 'C', 'D', 'E'],
['♪', '♫', '☻']
)
// Console loggings:
//   0, ['♠', 'A', '♪']
//   1, ['♡', 'B', '♫']
//   2, ['♢', 'C', '☻']
// Return value:
//   ['♠', 'B', '☻']
``````

## Putting the pieces together: solving FizzBuzz

With our two new skills – creating (imperfect) infinite arrays and mapping over multiple arrays – we are ready to tackle the fearsomely challenging puzzle of FizzBuzz.

### Step 1: rules of FizzBuzz as arrays

Let's translate the rules of FizzBuzz to arrays (if that makes sense). Let's also focus only on the first ten items of the FizzBuzz sequence for now.

• "Every 3rd item is 'Fizz'" means that every 3rd item of an array is `'Fizz'`. Other items are irrelevant, so they can be nulls:
``````[null, null, 'Fizz', null, null, 'Fizz', null, null, 'Fizz', null]
``````
• "Every 5th item is 'Buzz'" works similarly:
``````[null, null, null, null, 'Buzz', null, null, null, null, 'Buzz']
``````
• "Every 15th item is 'FizzBuzz'" can be ignored for now because the rule doesn't apply to a sequence of just ten items. But it would work similarly to the two previous rules/arrays.
• "Other items are numbers" is simply an array of numbers starting from 1:
``````[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
``````

Let's see how they look when put into the `multimap` function:

``````multimap(
(args) => {
console.log(args)
},
[null, null, 'Fizz', null,   null, 'Fizz', null, null, 'Fizz',   null],
[null, null,   null, null, 'Buzz',   null, null, null,   null, 'Buzz'],
[   1,    2,      3,    4,      5,      6,    7,    8,      9,     10]
)
// Console loggings:
//   [null,     null,  1]
//   [null,     null,  2]
//   ['Fizz',   null,  3]
//   [null,     null,  4]
//   [null,   'Buzz',  5]
//   ['Fizz',   null,  6]
//   [null,     null,  7]
//   [null,     null,  8]
//   ['Fizz',   null,  9]
//   [null,   'Buzz', 10]
// Return value:
//   [undefined × 10]
``````

Hmm, what if we take the first non-null item from each set of items?

`````` multimap(
(args) => args.find((x) => x !== null),
[null, null, 'Fizz', null,   null, 'Fizz', null, null, 'Fizz',   null],
[null, null,   null, null, 'Buzz',   null, null, null,   null, 'Buzz'],
[   1,    2,      3,    4,      5,      6,    7,    8,      9,     10]
)
//=> [1,    2, 'Fizz',    4, 'Buzz', 'Fizz',    7,    8, 'Fizz', 'Buzz']
``````

Hey, FizzBuzz! 😎 (Every 15th item will be handled soon.)

### Step 2: truthy vs falsy

The arrays contain only strings, numbers and nulls. Strings and numbers are truthy values, and nulls are falsy values.

Instead of taking the first non-null item, the first truthy item can be taken, and the result will be the same. This simplifies the callback function:

`````` multimap(
(args) => args.find(Boolean),
[null, null, 'Fizz', null,   null, 'Fizz', null, null, 'Fizz',   null],
[null, null,   null, null, 'Buzz',   null, null, null,   null, 'Buzz'],
[   1,    2,      3,    4,      5,      6,    7,    8,      9,     10]
)
//=> [1,    2, 'Fizz',    4, 'Buzz', 'Fizz',    7,    8, 'Fizz', 'Buzz']
``````

### Step 3: cycling 🚴‍♂️

The first two arrays are repeating, so they can be simplified with the new `cycle` function:

``````  multimap(
(args) => args.find(Boolean),
cycle([null, null, 'Fizz']),
cycle([null, null, null, null, 'Buzz']),
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
)
//=> [1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz']
``````

### Step 4: `range` function

The third array is lame because it's a manually-created range of numbers. Let's create a `range` function:

``````const range = (from, to) =>
Array.from({ length: to - from + 1 }, (_, i) => i + from)

range(5, 8)
//=> [5, 6, 7, 8]
``````

Usage:

`````` multimap(
(args) => args.find(Boolean),
cycle([null, null, 'Fizz']),
cycle([null, null, null, null, 'Buzz']),
range(1, 10)
)
//=> [1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz']
``````

### Step 5: `index` argument and filler arrays

The `range` function is not actually even needed.

The callback function passed to the `multimap` function accepts `index` as its second argument. Just like the callback function passed to `Array.prototype.map`.

By using the `index` argument, the third array (range of numbers) is not needed, and thus the `range` function is not needed.

But the third array can't just be omitted as all of the remaining arrays would be infinite:

``````  multimap(
(args, i) => args.find(Boolean) || i + 1,
cycle([null, null, 'Fizz']),
cycle([null, null, null, null, 'Buzz'])
)
//=> RangeError: Invalid array length
``````

Remember that the `multimap` function continues going over the arrays until any of them is exhausted. In other words, `multimap` goes over the arrays n times where n is the length of the shortest array.

Thus, `multimap` can be provided a "filler" array with a certain length. Because the other arrays are infinite, the filler array's length determines how many times `multimap` goes over the arrays.

We are not interested in the filler array's values, so an array with ten empty slots is fine:

``````  multimap(
(args, i) => args.find(Boolean) || i + 1,
cycle([null, null, 'Fizz']),
cycle([null, null, null, null, 'Buzz']),
Array(10) // [empty × 10]
)
//=> [1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz']
``````

The `range` function might be clearer, but I'm going to continue using the filler array, because filler arrays will be useful in the next steps.

The first two arrays are mostly fillers too. A more concise way of creating them is to create filler arrays and concatenating them with the last non-filler value:

``````// Before:
;[null, null, 'Fizz']
;[null, null, null, null, 'Buzz']

// After:
Array(2).concat('Fizz') // [empty × 2, 'Fizz']
Array(4).concat('Buzz') // [empty × 4, 'Buzz']
``````

Kinda like left-padding arrays... Someone quickly publish `left-pad-array` on npm!

Usage:

``````  multimap(
(args, i) => args.find(Boolean) || i + 1,
cycle(Array(2).concat('Fizz')),
cycle(Array(4).concat('Buzz')),
Array(10)
)
//=> [1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz']
``````

### Step 7: the remaining rule

The code works so far with Fizzes and Buzzes, but not with FizzBuzzes. Every 15th item is "Fizz" instead of "FizzBuzz":

``````  multimap(
(args, i) => args.find(Boolean) || i + 1,
cycle(Array(2).concat('Fizz')),
cycle(Array(4).concat('Buzz')),
Array(15)
)
//=> [1,
//    2,
//    'Fizz',
//    4,
//    'Buzz',
//    'Fizz',
//    7,
//    8,
//    'Fizz',
//    'Buzz',
//    11,
//    'Fizz',
//    13,
//    14,
//    'Fizz', // :( Should be 'FizzBuzz'
//   ]
``````

An easy fix is to use another array based on the remaining rule ("every 15th item is 'FizzBuzz'"):

``````  multimap(
(args, i) => args.find(Boolean) || i + 1,
cycle(Array(14).concat('FizzBuzz')),
cycle(Array(2).concat('Fizz')),
cycle(Array(4).concat('Buzz')),
Array(15)
)
//=> [1,
//    2,
//    'Fizz',
//    4,
//    'Buzz',
//    'Fizz',
//    7,
//    8,
//    'Fizz',
//    'Buzz',
//    11,
//    'Fizz',
//    13,
//    14,
//    'FizzBuzz', // :) Correct
//   ]
``````

But the new array is not actually necessary because Fizz + Buzz = FizzBuzz:

``````  multimap(
(args, i) => args.join('') || i + 1,
cycle(Array(2).concat('Fizz')),
cycle(Array(4).concat('Buzz')),
Array(15)
)
//=> [1,
//    2,
//    'Fizz',
//    4,
//    'Buzz',
//    'Fizz',
//    7,
//    8,
//    'Fizz',
//    'Buzz',
//    11,
//    'Fizz',
//    13,
//    14,
//    'FizzBuzz', // Still correct
//   ]
``````

## Finished result + playground

``````const cycle = (array) =>
new Proxy(array, {
get(target, prop) {
if (prop === 'length') return Infinity
if (/^\d+\$/.test(prop.toString())) return target[prop % target.length]
return target[prop]
},
})

const multimap = (fn, ...arrays) => {
const arrayLengths = arrays.map((array) => array.length)
const smallestArrayLength = Math.min(...arrayLengths)

return Array.from({ length: smallestArrayLength }).map((_, i) => {
const items = arrays.map((array) => array[i])
return fn(items, i)
})
}

const range = (from, to) =>
Array.from({ length: to - from + 1 }, (_, i) => i + from)

// Variation 1:
multimap(
(args) => args.find(Boolean),
cycle(Array(14).concat('FizzBuzz')),
cycle(Array(2).concat('Fizz')),
cycle(Array(4).concat('Buzz')),
range(1, 15)
)

// Variation 2:
multimap(
(args, i) => args.join('') || i + 1,
cycle(Array(2).concat('Fizz')),
cycle(Array(4).concat('Buzz')),
Array(15)
)
``````

There are a lot more variations to discover. For example, these two are nice:

``````multimap(
([fizz, buzz, number]) => [fizz, buzz].join('') || number,
cycle([null, null, 'Fizz']),
cycle([null, null, null, null, 'Buzz']),
range(1, 15)
)

multimap(
([word], i) => word || i + 1,
cycle([null, null, 'Fizz', null, 'Buzz', 'Fizz', null, null, 'Fizz', 'Buzz', null, 'Fizz', null, null, 'FizzBuzz']),
Array(15)
)
``````

Play around with the code on flems.io

What kind of cool variations can you come up with? Send me an email! 😎 (Address on the home page.)

## Acknowledgements

The approach and code in this blog post were like 90% inspired by the YouTube video FizzBuzz in Clojure with & without modulus/remainder/rest by Fred Overflow (cool name!).

I wanted to call this blog post "FizzBuzz in JS without the remainder operator," but I couldn't as I had to use the operator in the `cycle` function. ¯\(ツ)/¯