You are here: Home / Topics / Race Conditions and Concurrency in Software Development

Race Conditions and Concurrency in Software Development

Filed under: Solid System Design on 2026-01-02 13:08:06

Race Conditions & Concurrency

“If two correct actions happen at the same time and the result is wrong — you have a race condition.”

Today we move from duplicates (Day 2) to simultaneous execution, which is the #1 cause of “works in test, breaks in production”.

1️⃣ What Is a Race Condition (Plain English)

A race condition happens when:

  • Two or more executions run at the same time
  • They read shared data
  • They update based on stale values

Whoever “wins the race” overwrites the other.

2️⃣ The Classic Banking Example

Initial state:

Balance = 100

Two users at the same time:

Thread A reads balance → 100
Thread B reads balance → 100

Thread A deducts 70 → writes 30
Thread B deducts 40 → writes 60   ❌ WRONG

Both were “correct”. Final result is corrupted.

3️⃣ Why You Rarely See This in Development

  • Local machine → 1 user
  • Low latency
  • No retries
  • No real concurrency

Production:

  • Hundreds of threads
  • Network delays
  • Multiple instances

? Concurrency bugs hide until load.

4️⃣ Golden Rule of Concurrency (MEMORIZE)

Read → Check → Write must be atomic

If those 3 steps are not protected, corruption is guaranteed.

5️⃣ Where Race Conditions Occur

LayerExample
DatabaseLost updates
ApplicationShared variables
CacheStale values
MicroservicesDouble processing
Message queuesRedelivery

6️⃣ Database-Level Protection (MOST IMPORTANT)

? Pessimistic Locking (Strongest)

SELECT * FROM account WHERE id = 1 FOR UPDATE;

What it does:

  • Locks the row
  • Other transactions wait
  • No overwrite possible

Used by:

  • Banks
  • Inventory systems
  • Seat booking

⚡ Optimistic Locking (Scalable)

@Version
private Integer version;

If version changed → update fails.

Used when:

  • Reads are high
  • Writes are rare

7️⃣ Application-Level Mistakes ❌

if (balance >= amount) {
    balance -= amount;
    save(balance);
}

❌ This is NOT safe
Because read & write are separate.

8️⃣ Correct Safe Pattern ✅

Database-enforced update

UPDATE account
SET balance = balance - 70
WHERE id = 1 AND balance >= 70;

If rows updated = 0 → fail safely.

9️⃣ Spring Boot Safe Pattern

@Transactional
public void withdraw(Long id, int amount) {

    Account acc = repo.findForUpdate(id); // LOCK

    if (acc.getBalance() < amount)
        throw new InsufficientFundsException();

    acc.setBalance(acc.getBalance() - amount);
}

? Lock Ordering (Deadlock Prevention)

Always lock in same order:

Account A → Account B

Never:

A → B
B → A

11️⃣ Why synchronized Is Useless Here ❌

  • Works only in one JVM
  • Fails in clustered apps
  • DB remains unprotected

Concurrency safety must live in DB, not Java.

12️⃣ Mental Checklist for Every Write Method

Ask yourself:

  1. Can two threads run this at same time?
  2. Do they read shared data?
  3. Is read–check–write atomic?
  4. Is DB enforcing correctness?

If any answer is “no” → bug waiting.

? Mini Exercise

Take any update query and ask:

  • What happens if two users execute this simultaneously?
  • Who protects the data — code or DB?

? Day 4 Preview

Deadlocks — When Safety Itself Causes Failure

You’ll learn:

  • Why deadlocks happen
  • How to detect them
  • How banks avoid them

About Author:
N
Neha Sharma     View Profile
Hi, I am using MCQ Buddy. I love to share content on this website.