JavaScript does not support multithreading in the same way as the other programming languages do. However, there is a term race condition which means an unpleasant situation when the result of the program execution strongly depends on the order in which separate commands are performed.

Generally a race condition can occur when two or more threads are trying to get access to the same variable, at least one of them is willing to change a value of this variable and the threads don’t use any blocking. If these three conditions are met, the order of getting access to this variable becomes undefined which leads to various unexpected results. 

The question whether JavaScript can have a race condition or not is still open to debate.

Arguments against the race condition in JavaScript

Since JavaScript is a single-threaded language one may think that a race condition is not possible. For example, two synchronous functions in the code below are trying to access the same variable. There should be no problem since they will be executed one by one. While one operation is being performed nothing else can happen, all other actions are blocked until the current one is finished. 

bulb = "yellow";
syncToBlue(bulb);
syncToGreen(bulb);

In some cases synchronized code may be found disadvantageous. That’s where asynchronous code (callbacks and promises) is used. Returning to the previous example with the light bulb you can use two asynchronous functions and then things will get a bit more complicated. 

bulb = "yellow";
asyncToBlue(bulb);
asyncToGreen(bulb);

Since you don’t know the time returned by any of these functions, you could say they are located in a heap. If you run these functions from the same network, because asyncToBlue() was run first, it will get to the queue faster. In case you run asyncToBlue() from a network with a lower speed, asyncToGreen() will get to the queue faster, so no race condition is possible in these situations.

However, if asyncToBlue() is running from a network with the speed of A and asyncToGreen() has a speed of B, you could say that the race condition can happen only if A is equal to B. This situation depends on the external circumstances (like the speed of the network) and not the program itself.

In reality the case like this is quite rare, but even if it does occur, the nature of JavaScript does not allow two functions to be executed at the same time. They will be placed in a queue instead and will be performed in specific order anyway. 

Arguments for the race condition in JavaScript

Some situations prove that not only multithreading can be a reason for the race condition. Let’s create a program illustrating the possible existence of this issue. There will be a variable with the value of 0 and two functions trying to add 50 and 60 to it. To make things more interesting there will also be a promise for a random delay.

const randomDelay = () => new Promise(resolve =>
    setTimeout(resolve, Math.random() * 100)
  )

In the following code the loadAmount() function is also added to simulate waiting for a response from the database. Another function - saveAmount() - will simulate the delay caused by writing some information into a database.

amount = 0;
async function loadAmount () {
    await randomDelay();
    return amount;
  }
 
async function saveAmount (value) {
    await randomDelay();
    return amount += value;
  }

Now let’s create a function for adding 50 to a variable. The two functions, loadAmount() and saveAmount(), will be awaited in order to create an imitation of the possible delay. We will also log the amount to the console after each command.

async function addFifty(value)
{
    const amount = await loadAmount();
    console.log ("Adding fifty, the current amount is " + amount);
    const newAmount = amount + 50;
    await saveAmount(newAmount);
    console.log("The amount after adding 50 is " + newAmount);
}

The same concept can be applied to the function for adding 60 to the initial amount.

async function addSixty(value)
{
    const amount = await loadAmount();
    console.log ("Adding sixty, the current amount is " + amount);
    const newAmount = amount + 60;
    await saveAmount(newAmount);
    console.log("The amount after adding 60 is " + newAmount);
}

Finally, let’s create the main function which will run all these processes. Note how there is no await command while calling the functions for adding, which allows transaction2 to be scheduled before transaction1 is done. 

The await commands are implemented later to make sure both transactions are finished before loading of the final amount.

async function main () {
    const transaction1 = addFifty();
    const transaction2 = addSixty();
    await transaction1;
    await transaction2;
    const amount = await loadAmount();
    console.log("Final amount is: " + amount);
  }
  main()

If you run the whole program several times you will get different results. For example, the current amount for both functions can be 0. 

  // Possible outcome
  // Adding fifty, the current amount is 0
  // Adding sixty, the current amount is 0
  // The amount after adding 50 is 50
  // The amount after adding 60 is 60
  // Final amount is: 110

Of course, this example is a simple simulation, but it illustrates how the result of the program can be dependent on the order of operations execution. This is a proof that the race condition is possible in JavaScript even without multithreading. 

To avoid risky outcomes you need to make sure there is no possibility for processes to be executed at the same time. You can rewrite the previous function to make it safe as shown below. Another approach to resolving this issue is the use of Mutex.

async function main () {
   await addFifty(); 
   await addSixty(); 
    const amount = await loadAmount()
    console.log("Final amount is: " + amount);
  }

Summary

Race condition is a problematic situation when several processes or threads are trying to have access to the same resource. Although some programmers insist that there is no race condition in JavaScript, you can still face some situations when resource sharing can lead to unexpected results. That’s why it’s very important to be careful with the flow of events in your program.