JS - Concurrency
 · 3 min read
Imagine you have a bank account and 10 transactions arrive at the same time — some deposit money, some withdraw it.
How do you ensure that all transactions are processed correctly without overwriting each other?
let accountBalance = 100;
// Simulating Concurrent Transactions
async function runTransactions() {
  console.log(`Starting Balance: ${accountBalance}`);
  await Promise.all([
    addAmount(50),  // Should be 150
    addAmount(-30), // Should be 120
    addAmount(20),  // Should be 140
    addAmount(-10), // Should be 130
    addAmount(40)   // Should be 170 --> Only this returns final output
  ]);
  console.log(`Final Balance: ${accountBalance}`); 
  // Expected: 170, Actual: 140 as all transactions start with 100 balance
}
async function addAmount(amount) {
  const balance = await getBalance();
  const newBalance = balance + amount;
  console.log(
    "Read Balance:", balance,
    "| Adding:", amount,
    "| New Balance:", newBalance
  );
  
  await saveBalance(newBalance);
}
async function getBalance() {
  return accountBalance;
}
async function saveBalance(newBalance){
  accountBalance = newBalance;
}
runTransactions();
Read Balance: 100  | Adding: 50   | New Balance: 150 
Read Balance: 100  | Adding: -30  | New Balance: 70  
Read Balance: 100  | Adding: 20   | New Balance: 120 
Read Balance: 100  | Adding: -10  | New Balance: 90  
Read Balance: 100  | Adding: 40   | New Balance: 140
Final Balance: 140
- Each transaction reads 100 instead of the updated balance.
- Transactions compute their own new balance separately:
- Last transaction (+40) overwrites previous updates.
Challenges
How do we prevent multiple operations from interfering with each other?
- Locks? JavaScript doesn’t have built-in locks.
- Sequential execution? But we still want concurrency.
- Atomic updates? Possible, but how?
Solution: Amotic Updates using MUTEX
A Mutex (Mutual Exclusion) ensures that only one operation modifies the balance at a time.
function createMutex() {
  let locked = false;
  const queue = [];
  return {
    lock: function() {
      // Each lock call returns a promise
      //    the caller of lock, awaits for this to resolve
      return new Promise((resolve) => {
        if(!locked) {
          // No one holds the lock yet - take it
          
          locked = true;
          resolve();
        } else {
          // lock is taken - queue up the resolver
          
          queue.push(resolve);
        }
      });
    },
    unlock: function() {
      if(queue.length > 0) {
        // hand off the lock to the next waiting caller
        const nextResolve = queue.shift();
        nextResolve();
      } else {
        // no one is waiting - release the lock
        locked = false;
      }
    }
  }
}
const mutex = createMutex();
// ✅ Mutex-Protected Function
async function addAmountConcurrent(amount) {
  await mutex.lock();
  const balance = await getBalance();
  const newBalance = balance + amount;
  console.log(
    "💰 Read Balance:", balance, 
    "| Adding:", amount, 
    "| New Balance:", newBalance
  );
  await saveBalance(newBalance)
  mutex.unlock();
}
let accountBalance = 100;
async function getBalance() {
  return accountBalance;
}
async function saveBalance(newBalance) {
  accountBalance = newBalance;
}
async function runTransactionsMutex() {
  console.log(`Starting Balance: ${accountBalance}`);
  await Promise.all([
    addAmountConcurrent(50),  
    addAmountConcurrent(-30), 
    addAmountConcurrent(20),  
    addAmountConcurrent(-10), 
    addAmountConcurrent(40)   
  ]);
  console.log(`💰 Final Balance: ${accountBalance}`); 
}
Fixed final output
runTransactionsMutex();
💰 Read Balance: 100  | Adding: 50   | New Balance: 150 
💰 Read Balance: 150  | Adding: -30  | New Balance: 120 
💰 Read Balance: 120  | Adding: 20   | New Balance: 140 
💰 Read Balance: 140  | Adding: -10  | New Balance: 130 
💰 Read Balance: 130  | Adding: 40   | New Balance: 170
 
💰 Final Balance: 170