Cycle Through an Array of Values with the Modulus Operator

8 min read

Sometimes when we have an array of values, we need the ability to progress through the items one at a time. When we reach an upper or lower bound (one of the “ends”) we can either prevent movement in the same direction, or we can circle back around to the value at the far end and allow progression to continue in an (potentially) endless loop.

Imagine progressing through a collection of images in a carousel, or looping through the cards in a deck of cards, where the previously selected card just gets put back on the bottom of the deck.

There are many ways to solve for this, but in this post I’m going to walk you through how we can solve this in JavaScript with math, by way of the modulus operator (%).

Initial Setup

Let’s start with a single-component React app to work from:

import { useState } from 'react'
import './styles.css'

const data = ['One', 'Two', 'Three', 'Four']

export default function App() {
  const [current, setCurrent] = useState(0)
  function handleNext() {
    setCurrent((cur) => cur + 1)
  }

  function handlePrev() {
    setCurrent((cur) => cur - 1)
  }

  return (
    <div className="App">
      <h1>Array Cycle with Modulus</h1>
      <h2>{data[current]}</h2>
      <div style={{ display: 'flex', gap: '2rem', justifyContent: 'center' }}>
        <button onClick={handlePrev} type="button">
          &lt; Prev
        </button>
        <button onClick={handleNext} type="button">
          Next &gt;
        </button>
      </div>
    </div>
  )
}

We’ve got an array of strings called data, and in our React app, we have a piece of state that contains the currently selected index called current.

We’ve also rendered two buttons labeled “Prev” and “Next” that decrement and increment that current value respectively.

As it stands, we can use the buttons to progress forward and back, as long we stay within the bounds of the array. When we reach the end of the array, our current value is 3 and we display the string “Four” in the UI. If we click “Next” again, current will go to 4 and we’ll be trying to access a value that is outside the array’s bounds.

Let’s take a look at how we can solve this with the modulus operator.

Quick Refresher on Modulus

If you’re comfortable with % in JavaScript, you can jump to the next section without missing out.

Let’s briefly just remind ourselves of what the modulus operator does. We frequently use it to check if a value is evenly divisible by another value. Take this isEven utility

const isEven = (num) => num % 2 === 0

Running this yields predictable results;

isEven(1) // false

isEven(2) // true

isEven(4) // true

isEvent(7) // false

What this is saying is take num, divide it by 2 and return the remainder. So when we run isEven on 2 or 4, it divides evenly and results in 0. When we do the same thing with 3, it goes in once and leaves a remainder of 1. When we compare the result of num % 2 to 0, we get back our boolean value.

Our result is the remaining integer value after division, so if we apply this to values other than 2 we’ll get results beyond just 1 or 0. So 5 % 3 would result in 2 and 22 % 9 would result in 4.

With that bit of context under our belts, let’s get back to the example.

Reset current to 0 for “Next”

We’ll focus on the “Next” functionality first. Let’s look at what happens with the modulus operator for some of the values we’re working with in our example.

Based on the sample data, our valid index values are 0, 1, 2, 3 and the length of the array is 4. As soon as the index value reaches the length of the array, we’ve gone out of bounds.

Let’s Look at the Math

Let’s see what we can do with our length value (4), modulus, and our index values.

0 % 4 // 0
1 % 4 // 1
2 % 4 // 2
3 % 4 // 3

What’s happening here? Well, it turns out that when the dividend is smaller than the divisor1 in a modulus operation, the result of the expression is the dividend. So essentially, writing index % arrayLength will give us back our index value when it is one of our valid index values, that is, when it is less than the length of the array.

Now let’s look at what happens when we use the length as both dividend and divisor:

4 % 4 // 0

This was a pretty predictable result, a number divides into itself 1 time with no remainder, so the result will always be 0.

This means that if we always increment by one, we’ll automatically jump to 0 when we progress past the end of the array, sending us back to the beginning.

Apply the Math to the React Example

Let’s apply this to the “Next” button in the React example by updating the handleNext function

function handleNext() {
- 	setCurrent((cur) => cur + 1)
+ 	setCurrent((cur) => (cur + 1) % data.length)
}

We get our target index value by incrementing the current value, then add apply our mod with the length of the array. Now when we try to progress to an index one beyond the valid values, we reset that current value to 0. This way, we can use “Next” to cycle through the array and it loops around on itself.

Applying This In the Opposite Direction

Now let’s take a look at how this works going the other way. If we’re at the start of our array, our current index is 0 and our logic for moving backwards will decrement our value and result in the current value being -1. Let’s look at the numbers to start, then we’ll apply it to our example.

Starting with the Math Again

If we have an index value of -1, we ideally want that to be converted into the last item in the array, so -1 needs to become 3. As luck would have it, the length of our array minus 1 will always result in the index of the last item. And if we apply that value to our mod operation, we should get that value back (because it’ll be 3 and 3 % 4 will give us back 3)

;(-1 + 4) % 4 // 3

Now if we apply that logic to a value that isn’t at the extreme end of our array bounds, we’ll see that it still yields the desired result

;(2 + 4) % 4 // 2

Here, we add the index (2) and length (4) to end up with 6 % 4, which results in 4 going into 6 once, with a remainder of 2, so we essentially get that target index value back.

If we carry this logic through the other values, decrementing our index value as we go will result in the following:

;(1 + 4) % 4 // 1
;(0 + 4) % 4 // 0

Applying it in React

Now we can update the React example and we’ll be able to infinitely loop through our array in both directions.

We’ll keep the decrement in place to get our next target index value, then add the length of the array and apply the mod operation to the resulting value.

function handlePrev() {
-   setCurrent((cur) => cur - 1)
+	setCurrent((cur) => (cur - 1 + data.length) % data.length)
}

The Completed Solution

Here’s the code for that component with both “Next” and “Prev” fully wired up with our mod operations.

import { useState } from 'react'
import './styles.css'

const data = ['One', 'Two', 'Three', 'Four']

export default function App() {
  const [current, setCurrent] = useState(0)
  function handleNext() {
    setCurrent((cur) => (cur + 1) % data.length)
  }

  function handlePrev() {
    setCurrent((cur) => (cur - 1 + data.length) % data.length)
  }

  return (
    <div className="App">
      <h1>Array Cycle with Modulus</h1>
      <h2>{data[current]}</h2>
      <div style={{ display: 'flex', gap: '2rem', justifyContent: 'center' }}>
        <button onClick={handlePrev} type="button">
          &lt; Prev
        </button>
        <button onClick={handleNext} type="button">
          Next &gt;
        </button>
      </div>
    </div>
  )
}

Conclusion

When you see this for the first time without any explanation, it can be a bit confusing. At least it was for me. I don’t like using code I don’t understand because when something goes sideways, I want to know I can jump in and fix a problem. So when I first encountered this one, I thought it was important to dig into how it worked. Once I did, I wanted to make sure I understood it well enough to explain it. I hope this managed to do that and that you’re able to apply this solution, cut down on what would otherwise turn into some cumbersome logic, and still understand how it works.

If video is your thing, you can also see a video walk-through of this in my egghead video on the same topic

Footnotes

  1. You can read up on dividend and divisor here if you need a reminder of which is which.