Skip to content Skip to footer

How to handle infinite loops within user code

Infinite loops are a pain. In general, running an infinite loop can eat up your computer’s resources and then freeze your IDE—and sometimes even your whole machine. Inside the CodeSignal IDE, letting infinite loops run unchecked would eventually slow down or crash the UI. These UI issues would make things more difficult for our users and affect the quality of assessments, so we needed to ensure we provide a responsive and useful programming environment. 

To that end, we had to find a way to gracefully handle the problem of users writing infinite loops into their code. Even if the halting problem proves that we could never perfectly detect infinite loops, by focusing on solving this problem for our specific product and infrastructure, we found solutions that protect our users’ experiences.

Handling Infinite Loops

Basic Solution: Code Instrumentation

Code instrumentation is a common solution for handling infinite loops. Basically, an instrumentation tool can monitor and break infinite loops. We maintained the loop-protect package in our codebase, which rewrites JavaScript code to break loops that have been running too long. While a more complicated analysis may have enabled us to detect (some) instances of infinite looping analytically, for our CodeSignal IDE, we could go with a simpler solution and place a reasonable timeout on how long a loop should be able to run. We avoid the halting problem because we don’t need to decide if any piece of code will eventually terminate—if it would take a year for a candidate’s code to finish running, it’s probably not useful for their assessment.

However, this code instrumentation tool could only handle infinite loops caused explicitly by the `while`, `for`, and `do…while` keywords. Unfortunately, infinite loops can also be introduced implicitly. For example, a frontend React task could introduce an infinite loop by using and updating the same state together in the `useEffect` hook: 

 const [count, setCount] = useState(-1);
 useEffect(() => setCount(count + 1), [count]);

Here, updating count will call `useEffect`, which will update `count`, which will call `useEffect`, and so on forever

This type of infinite loop would not be caught by the `loop-protect` code instrumentation.

Customized Solution: Checking In with Pings

I looked for a standard solution or package to handle these kinds of infinite loops that stemmed from the improper use of frontend frameworks, but I found nothing in my search. Since there was no general solution, I instead started thinking about some customized approaches based on CodeSignal’s infrastructure.

Fortunately, I found a way that our infrastructure would allow us to handle this type of infinite loop. First, some context: In our frontend, the UI is rendered using an iframe, and the iframe is hosted on a different server, with a different domain, over which we have full control. Nowadays, most modern browsers will run an iframe from a different domain in a separate thread from the main app. When there is an infinite loop, then, only the resources in the iframe thread will be eaten up, while other threads can run as normal. 

Based on this, I implemented a simple ping mechanism: The iframe thread regularly pings the thread that runs the React app, and if the React app doesn’t receive the ping within a certain timeframe, it stops rendering the iframe. 

So, when an infinite loop happens, more and more resources in the iframe thread get consumed. This causes longer and longer delays when triggering the next interval, and thus, the ping to the React thread is delayed. On the React thread, it checks whether it receives a ping from the iframe thread on a regular cadence. If it doesn’t receive the ping before it times out, we assume there’s an infinite loop ongoing and halt the iframe’s rendering. With this mechanism in place, the user’s browser tab doesn’t freeze due to the infinite loop. 

It’s important to find the right balance here. The timeout needs to be long enough that reasonable solutions will be able to run even if they’re slow and cause a minor slowdown in the iframe thread. If we let infinite loops run for too long, though, it is possible that the whole browser will freeze before the timeout is triggered. 

Here’s a pseudocode example of how to set up this ping: 

iframeScript.js

setInterval(() => {
   // send a ping event to the React thread every 2 sec
   parent.postMessage(
     {
       action: 'iframe-ping',
     },
     '*'
   );
 }, 2000);

React component

Class IdeComponent {
 componentDidMount() {
  setInterval(() => {
     If (!this.isIframePing) {
        // halt iframe
        iframe.src = ‘’;
     } else {
        this.iframePing = false;
     }
  }, 10000)
 
 
  window.addEventListener('message', this.processMessage, false);
 }
 
 processMessage(message) {
  if (message.data.action === ‘iframe-ping’) {
    this.isIframePing = true;
  }
 }
}

In the end, this bespoke solution came from our understanding of what our product requirements are and what our infrastructure could support. The halting problem is fascinating and important, but for our needs we didn’t have to solve the whole thing (lucky for us, because that’s literally impossible).

If you love to build great products and find solutions to thorny problems, we’re hiring. Join us, and hopefully someday we’ll be able to show off your own ingenuity right here on this blog!  


Zhoucheng Li is a Software Engineer at CodeSignal. In his work, he focuses on designing and building features in CodeSignal’s products using different tech stacks.