Prevent infinite loops in JavaScript
Tags: javascript, beginners
Published: 2021-07-02 09:26:40

There are some typical beginner (YES, AND EXPERT-) errors in JavaScript that can be a real pain: **infinite loops** :infinity:

They occur, when the control structures have no condition to end (branch out) and thus execute indefinitely.

This guide covers some causes of infinite loops and how to prevent them.



[1. The no-brainer](#no-brainer)
[2. The forgotten increment / decrement](#forgotten-inc-dec)
[3. The uncontrollable source of truth](#uncontrollable-source)
[4. The unintended override](#unintended-override)
[5. The unorthodox breaking condition](#unorthodox-condition)
[6. More options to prevent infinite loops](#more-options)


<a name="no-brainer"></a>
<hr>
## 1. :infinity: The no-brainer

If you never have encountered an infinite loop, simply run the following code. Do not use your developer console to directly execute code but use online tools like CodePen, otherwise you may have to force-quit the whole browser process (depending on your os, browser and version):

```javascript
while (true) {
  console.log('pleas wait just one more second')
}
```

or even shorter:

```javascript
while (true);
```

That's a nasty experience and when one of your users gets into this situation, you can be sure you have just lost her.


### How to prevent

Run brain.exe before coding. I think the vast majority will not face this one unless intentionally done.

If this really happens to you by accident: it's time to go home for today.



<a name="forgotten-inc-dec"></a>
<hr>
## 2. :infinity: The forgotten increment / decrement 

This is a classic one and even experienced developers fall into it from time to time, especially when working too long and concentration(tm) has left the chat:

```js
let i = 0

while (i < 10) {
  console.log(i)
  // i++ is missing
}
```

Similar with decrementing:

```js
let i = 9

while (i >= 0) {
  console.log(i)
  // i-- is missing
}
```


### Prevent it using auto-increment / auto-decrement

For beginners this might be confusing, due to way how pre-increment and post-increment (and *-decrement) work.

Suggestion: [read it up first](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Increment#examples) and then use it directly in your while conditional:

```js
let i = 0

while (i++ < 10) {
  console.log(i) // will be 1,2,3,4,5,6,7,8,9,10
}
```

As you can see, this will not count `i` from `0 ... 9` so we need to fix the indices:

```js
let i = -1

while (i++ < 9) {
  console.log(i) // will be 0,1,2,3,4,5,6,7,8,9
}
```

Yes, I know it becomes rather confusing than it helps. This is because the `i` is incremented **before** the body is executed (as opposed to `for` loops, where it will be incremented **after** the body has been executed). Just keep it in mind when you next time design a while loop with auto-increment.


With pre-increment (already range-corrected):


```js
let i = -1

while (++i <= 9) {
  console.log(i) // will be 0,1,2,3,4,5,6,7,8,9
}
```

A good exercise in between: Implement the same while loop with auto pre-decrement (`--i`) and auto post-decrement (`i--`).


<a name="uncontrollable-source"></a>
<hr>
## 3. :infinity: The uncontrollable source of truth

Sometimes you use `while` loops to do some operation until a condition is met and where the breaking condition is not based on numerical values. 

If the source of this condition is hardly determinable (as opposed to counters with a numerical limit) you may face infinite loops. The worst: in rare cases these may occur only in a few situations to a few users and debugging sessions will be long and exhaustive!

```js
let ended = false

while (!ended) {
  // do stuff
  ended = getIsEnded() // ticking time-bomb
}
```


### Use a safety counter to prevent this

If you really can't redesign this one towards a more determined condition then you may introduce some kind of safetey-counter.

This counter will be the super-most-upper-maximum of iterations that run and if it's reached you expect the loop to run into infinity mode and throw an Error to prevent this:

```js
let ended = false
let safety = 0
const maxSafety = 1000

while (!ended && safety++ < maxSafety) {
  // do stuff
  ended = getIsEnded() // just tick...
}

if (!ended) {
  throw new Error('Infinite loop detected and prevented')
}
```


<a name="unintended-override"></a>
<hr>
## 4. :infinity: The unintended override

Let's say your code gets more and more complex and you will face situations, where your counter or condition is overridden or altered then you may not realize that this may lead into infinite loops:

```js
const array = [0,1,2,3]
for (let i = 0; i < array.length; i++) {
  // do stuff...
  array.push(-1) // boom
}
```

Another example:

```js
const obj = { count: i, max: 10 }
const increment = obj => {
  obj.count++
  obj.max++ // unintended but still boom
}

while (obj.count < obj.max) {
  // do stuff
  increment(obj)
}
```

While this example is somewhat exotic and I suggest to never do such constructs it shows that some of JavaScript's features (pass object by reference) used the wrong way can easily cause trouble.


### Prevent using immutable maximum

Using a `const` for maximum values makes it much harder to manipulate the upper bounds:

```js
const array = [0,1,2,3]
const length = array.length

for (let i = 0; i < length; i++) {
  // do stuff...
  array.push(-1) // who cares
}
```

Some goes for the while loop:

```js
const max = 10
const obj = { count: 0 }
const increment = obj => {
  obj.count++
}

while (obj.count < max) {
  // do stuff
  increment(obj)
}
```

However, just don't use this second example at all and better rewrite your code to use independent variables:

```js
const max = 10
let i = 0

while (i < max) {
  // do stuff
  i++
}
```


<a name="unorthodox-condition"></a>
<hr>
## 5. :infinity: The unorthodox breaking condition

You can create some crazy complex conditions for breaking loops. This could also potentially cause infinite loops.

Consider a loop that breaks only if the counter is exactly a specific value (as opposed to using less-than or greater-than):

```js
for (let i = 0; i !== 5; i++) {
  console.log(i) // 0,1,2,3,4
}
```

Yes it works and breaks as expected. But what if your counter is not incremented using the `++` operator but, say using `+= 3`?

```js
for (let i = 0; i !== 5; i += 3) {
  console.log(i) // 0,3,6,9,12,15...
}
```


### Prevention options

First you can introduce a safety counter (as shown before) or add a more determinable condition:


```js
for (let i = 0; i !== 5 && i < 10; i += 3) {
  console.log(i) // 0,3,6,9,12,15...
}
```

Try to avoid breaking conditions that introduce the possibility to never occur. 



<a name="more-options"></a>
<hr>
## 6. :wrench: More options to prevent infinite loops


### Iterate over iterable

Iterables are great as they can be safely iterated via `for..of` and never cause infinite loops **when only reading**:

```js
for (const num of [0,1,2,3,4]) console.log(num) // 0,1,2,3,4
for (const char of 'hello') console.log(char) // h,e,l,l,o
for (const name of new Set(['jane','john'])) console.log(name) // jane, john
```

Note, however, that extending / altering the structures during the loop will still be a potential cause for infinity loops!


### Use a safe for-loop

The easiest way to prevent the loop is to use conditions that  are always determined. The following for-loops are very good examples of that:


**Iterate n-times in forward direction**

```js
for (let i = 0; i < 10; i++) { ... }
```

**Iterate n-times in backwards direction**

```js
for (let i = 9; i >= 0; i--) { ... }
```

In both cases the loops will always run through (unless you try to manipulate `i` inside the body but I assume you know that would be a very dangerous thing to do).

**Use a function with "safe-iteration"**

You can write a function, that implements a loop in a safe way and which executes a given function in each step:

```js
const loop = ({ n, fct, backwards }) => {
  let i

  if (backwards) {
    for (i = n - 1; i >= 0; i--) fct(i)
  }
  
  // default mode is forward
  else {
    for (i = 0; i < n; i++) fct(i)
  }
}

// usage
loop({ n: 5, fct: i => console.log(i) }) // 0,1,2,3,4
loop({ n: 5, fct: i => console.log(i), backwards: true }) // 4,3,2,1,0
```


### Use a safe while-loop

The following function is an example of a while loop, wrapped in a more safe environment that will prevent infinite loops:

```js
const safeWhile = ({ condition, fct, max = 1000 }) => {
  let i = 0
  let value // cover optional return value

  while (condition(i)) {
    if (i++ >= max) {
      throw new Error('Infinite loop detected and prevented')
    }
    value = fct(i)
  }
  
  return value
}

// usage
safeWhile({
  condition: i => true,
  fct: i => {}
})
// throws error but is never infinite
```


## Summary

I hope this collection of causes and fixes will help you to write more robust code and prevent these nasty infinite loops at all cost to maximize functionality and stability of your applications.

If you think there are concepts missing, confusing or simply wrong, please leave a comment so the article can be improved for everyone :heart: