There are some typical beginner (YES, AND EXPERT-) errors in JavaScript that can be a real pain: infinite loops ♾️
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
2. The forgotten increment / decrement
3. The uncontrollable source of truth
4. The unintended override
5. The unorthodox breaking condition
6. More options to prevent infinite loops
1. ♾️ 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):
while (true) {
console.log('pleas wait just one more second')
}
or even shorter:
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.
2. ♾️ 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:
let i = 0
while (i < 10) {
console.log(i)
// i++ is missing
}
Similar with decrementing:
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 and then use it directly in your while conditional:
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:
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):
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--
).
3. ♾️ 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!
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:
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')
}
4. ♾️ 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:
const array = [0,1,2,3]
for (let i = 0; i < array.length; i++) {
// do stuff...
array.push(-1) // boom
}
Another example:
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:
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:
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:
const max = 10
let i = 0
while (i < max) {
// do stuff
i++
}
5. ♾️ 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):
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
?
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:
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.
6. 🔧 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:
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
for (let i = 0; i < 10; i++) { ... }
Iterate n-times in backwards direction
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:
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:
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 ❤️
Top comments (0)