Improving Front-End Memory Usage by 30x on Carousel Loading

In Q2 of 2020, we had a tech refactor on the experiences page, rewriting into React and consuming REST API. But during the implementation, we found an interesting problem.

There are around 20 carousels on the experiences page, each with at least 60 tiles. So the page will render over 1,200 experiences, including relevant information such as their title, user rating, concurrent users, and thumbnail. In each carousel, clicking on the right scroll will send a request to load the next batch of experiences if there are more. Then the new data resulting from the request will be appended onto the carousel, meaning more nodes added into the DOM and more thumbnails rendering into the browser. The memory usage inside the browser will grow as users scroll to request more data, and then the UX will slow down and begin to lag.

Memory Profile

We can use the Chrome Developer Tools to profile this issue. The first snapshot is taken when the page initially loads, and the second one is taken after the user clicks the next scroll in one carousel 12 times. We see a nearly 9.7MB increase in memory usage from 100+ string nodes and increased thumbnail rendering.

Solution

When the carousel scrolls, users only view the items from the carousel window. For example, in a 1920×1080 resolution laptop, under the full screen browser, there will be only ~9 visible items each time. Therefore, it is unnecessary to render all invisible nodes and thumbnails together or to carry more data returned from the request into the DOM.

So, here’s the idea: after we fetch the raw data from the API request for the first time, we build the same length of array preparing for rendering into the DOM. Inside the rendering array, we can populate only enough data for filling the display and scrolling to next. The rest of the array will be filled only when the display window is scrolled close to it. Let’s give two indexes to record the range of rendering data: renderingStartIndex and renderingEndIndex. We set up a cursor to tell which start position in the list is visible. When scrolling, we need to continually update the cursor to the index of the first visible item. Before the scrolling animation is finished, we will need to check if the next cursor is inside of the rendering data array. According to carousel window size and card size, we can easily tell how many visible items will fit in the display window. Then, we need to make sure the items from the current window and next window are inside the range of the rendering data array. If not, we grab items from raw data to fill the empty list while also clearing out the data from before the cursor position. This will apply when users scroll to the right or left. When users scroll to the right, by fetching more data in the background, we will fill in the raw data each time after we receive new data. However, raw data won’t be involved in page rendering.

Here is how it looks with the improvement:

Memory Profile

Let’s look at the Chrome Developer Tools again. As before, the first snapshot is taken at the initialized time and the second one is taken after scrolling to the right at 12 times. Now, loading 100+ items costs only 0.3MB of increased memory, as opposed to 9.7MB before.

What’s Next?

Besides improving the carousel component to handle large data windows, when we refactor a feature/page, we need to add more benchmarks to catch the performance information.

While the modern web application stack has been shifting toward the front end, capturing and improving the front-end telemetry is as important as traditional back-end telemetry.


Ying Jiang is a Principal Frontend Software Engineer at Roblox. As Roblox’s first Frontend Engineer, she works to introduce modern frontend tech stack into Roblox and improve the development and deployment pipeline. She has also worked on real-time features such as chat, notification stream, and voice.

Neither Roblox Corporation nor this blog endorses or supports any company or service. Also, no guarantees or promises are made regarding the accuracy, reliability or completeness of the information contained in this blog.