Concurrency Visualizer
Interactive exploration of race conditions and timing dependencies
How to Use This Tool
Quick Start
- 1.Choose a scenario from the five available examples (Counter, Bank Account, Singleton, Cache, Deadlock)
- 2.Select an execution ordering to see how different thread interleavings affect the outcome
- 3.Step through the execution using Play/Pause or manual Next/Previous buttons
- 4.Observe the state changes in both shared and thread-local state panels
Understanding the Display
Shared State (Purple)
This shows data that all threads can access. In real systems, this could be a database, shared memory, cache, or file. Race conditions occur when multiple threads read/write this state without proper synchronization.
Thread Local State (Green)
This shows data that is private to each thread. Each thread has its own copy of local variables. Notice how threads can have different local values even when they read from the same shared state at different times.
Key Concepts to Learn
Race Condition
When the outcome depends on the unpredictable timing of thread execution. The same code can produce different results on different runs.
👉 Look for: Steps marked with ⚠️ warning symbols, especially when the final state differs between execution orderings.
Lost Update Problem
When one thread's update overwrites another thread's update because both read the old value before either writes. Common in databases and concurrent modifications.
👉 Look for: Two threads reading the same initial value, then both writing back modified versions - the last write wins and the first is lost.
Check-Then-Act Pattern
Testing a condition and then acting on it in separate steps. The condition can change between the check and the act, causing bugs. Seen in singleton patterns, file operations, and caches.
👉 Look for: The singleton example where both threads check "is null?" and both see true, leading to duplicate object creation.
Deadlock
When two or more threads wait for each other to release resources, creating a circular dependency that never resolves. The system freezes completely.
👉 Look for: The deadlock example where T1 holds A and waits for B, while T2 holds B and waits for A - neither can proceed.
Timing Dependencies (Distributed Systems)
In distributed systems, operations take time to propagate. A read might happen before an update completes, leading to stale data or inconsistencies.
👉 Look for: The cache example shows delay times (+50ms, +100ms). Notice how network delays affect which data gets read.
What to Watch For
Learning Tips
- •Compare orderings: Run through all execution orderings for each scenario. Notice how the same code produces different results.
- •Focus on state transitions: Pay attention to when shared state changes and when thread-local state differs from shared state.
- •Identify the critical section: In each example, find the sequence of read-modify-write steps that need to be protected.
- •Think about real systems: Consider how these patterns apply to databases, web servers, APIs, and other systems you work with.
- •Use step-by-step mode: Don't just watch the auto-play. Manually step through to understand each state change.
Common Solutions
While this tool focuses on understanding the problems, here are typical solutions used in real systems:
Ensure only one thread accesses the critical section at a time
Hardware-supported operations that complete without interruption
Detect conflicts after they occur and retry the operation
Why This Matters
This is the most fundamental concurrency bug. It appears in web analytics, inventory systems, like counters, and any shared accumulator. Even simple operations like count++ aren't atomic!
Real-world example: A website tracking page views where multiple servers increment the same counter in a database without proper locking.
Code
function incrementCounter() {
let temp = counter; // Read
temp = temp + 1; // Increment
counter = temp; // Write
}Shared State
Thread Local State
No local state yet