facinick

Understanding the JavaScript Modulo Operator

When I was first learning to code, I remember finding the Modulo operator (%) extremely confusing. 😬

If you don't understand what it's doing, the values it produces seem completely random:

1const what = 10 % 4; // 2
2const the = 10 % 10; // 0
3const heck = 4 % 10; // 4
1const what = 10 % 4; // 2
2const the = 10 % 10; // 0
3const heck = 4 % 10; // 4

In this blog post, we're going to learn how this operator works by refining our mental model for division. We'll also cover a practical, every-day use case for this curious fella.

Rethinking division

Suppose we have the following bit of arithmetic:

112 ÷ 4
112 ÷ 4

Division can often feel pretty abstract or theoretical, but there's a practical way to think about it: we want to divide a number into equally-sized groups.

Drag the slider to see how this operation can be visualized:

1

12 ÷ 1 = 12

12 ÷ 4 evaluates to 3, because each group holds exactly 3 items. Essentially, we're figuring out how many items will be held inside each group.

In the example widget above, our dividend (the number to be divided) is 12. 12 is a remarkably clean number when it comes to division; it can be split neatly in lots of different ways.

Suppose we had the following equation instead:

111 ÷ 4
111 ÷ 4

This equation evaluates to 2.75. Each group has 2 complete items, and then ¾ths of another item.

This works if we're dividing up pizzas or cakes… but what if the items are indestructible? What if we can't break each item up into smaller fractions?

In that case, we'd be able to fit 2 items into each group, and we'd be left with 3 additional items:

This is known as the remainder. It's what the modulo operator produces.

4

Remainder Area

11 ÷ 4 = 2 (and 3 leftover)

Weighted Quick Union Find Algorithm Use-case

Percolating Grid

System doesn't percolate

In cases where the number can be equally divided into groups (eg. 12 ÷ 4), there is nothing left over:

112 % 4; // 0
112 % 4; // 0

In situations where the dividend (the number to be divided) can't be split equally into groups, the modulo operator lets us know how much is left over:

111 % 4; // 3
111 % 4; // 3

A real-world use case

So, I'm not a mathematician, I'm a web developer. All of this math stuff is interesting, but let's talk about how the modulo operator can come in handy on the web.

Specifically, there's one sort of problem that I seem to run into a lot, where the modulo operator offers the perfect solution: circular arrays.

For example, suppose we have an array of 3 colors. Each second, we want to switch to the next color in the list. When we reach the end of the list, we want to jump back to the first item:

  • red
  • yellow
  • blue
Time Elapsed
0

This is a surprisingly tricky problem. Suppose we have a variable called timeElapsed that starts at 0 and increments by 1 every second; we have to somehow map this ever-increasing value to an array with only 3 items.

Essentially, we need to write a function that produces the following results:

1const COLORS = ['red', 'yellow', 'blue'];
2
3getColor({ timeElapsed: 0 }); // 'red'
4getColor({ timeElapsed: 1 }); // 'yellow'
5getColor({ timeElapsed: 2 }); // 'blue'
6getColor({ timeElapsed: 3 }); // 'red'
7getColor({ timeElapsed: 4 }); // 'yellow'
8getColor({ timeElapsed: 5 }); // 'blue'
9getColor({ timeElapsed: 6 }); // 'red'
10getColor({ timeElapsed: 7 }); // 'yellow'
11getColor({ timeElapsed: 8 }); // 'blue'
12// ...And so on, forever
1const COLORS = ['red', 'yellow', 'blue'];
2
3getColor({ timeElapsed: 0 }); // 'red'
4getColor({ timeElapsed: 1 }); // 'yellow'
5getColor({ timeElapsed: 2 }); // 'blue'
6getColor({ timeElapsed: 3 }); // 'red'
7getColor({ timeElapsed: 4 }); // 'yellow'
8getColor({ timeElapsed: 5 }); // 'blue'
9getColor({ timeElapsed: 6 }); // 'red'
10getColor({ timeElapsed: 7 }); // 'yellow'
11getColor({ timeElapsed: 8 }); // 'blue'
12// ...And so on, forever

Let's look at how the modulo operator can help us solve this problem:

1const COLORS = ['red', 'yellow', 'blue'];
2
3function getColor({ timeElapsed }) {
4 const colorIndex = timeElapsed % COLORS.length;
5
6 return COLORS[colorIndex];
7}
1const COLORS = ['red', 'yellow', 'blue'];
2
3function getColor({ timeElapsed }) {
4 const colorIndex = timeElapsed % COLORS.length;
5
6 return COLORS[colorIndex];
7}

Miraculously, this does exactly what we need! This method will always return one of the 3 colors, as long as timeElapsed is an integer. And it'll cycle through the 3 colors as timeElapsed increases.

COLORS.length is equal to 3, since there are 3 colors in our array. And so, as timeElapsed increments from 0 to 8, this function winds up performing the following sequence of calculations:

1const colorIndex = 0 % 3; // 0
2const colorIndex = 1 % 3; // 1
3const colorIndex = 2 % 3; // 2
4const colorIndex = 3 % 3; // 0
5const colorIndex = 4 % 3; // 1
6const colorIndex = 5 % 3; // 2
7const colorIndex = 6 % 3; // 0
8const colorIndex = 7 % 3; // 1
9const colorIndex = 8 % 3; // 2
1const colorIndex = 0 % 3; // 0
2const colorIndex = 1 % 3; // 1
3const colorIndex = 2 % 3; // 2
4const colorIndex = 3 % 3; // 0
5const colorIndex = 4 % 3; // 1
6const colorIndex = 5 % 3; // 2
7const colorIndex = 6 % 3; // 0
8const colorIndex = 7 % 3; // 1
9const colorIndex = 8 % 3; // 2

We can then use this colorIndex to look up the color from the COLORS array. It's guaranteed to always cycle within the range of available indexes for that array.

To understand why this works, it's worth remembering our new model for division: we're trying to divide timeElapsed into 3 equally-sized groups, without any fractional or decimal values. The remainder will always be either 0, 1, or 2. It will never be 3+, because if there was 3 left, we could fit 1 more in each group!

Essentially, it's as if we had the ability to create a “circular” array. No matter how large our underlying timeElapsed value grows, we can have it cycle indefinitely through the colors in the COLORS array.

In my opinion, this trick alone makes the modulo operator worth learning! I've used this circular-array trick dozens of times over the years, and it's just one of several practical use cases for this handy operator.

Published By