Concurrency & Async
Event loop, goroutines, race conditions. Why one thread can serve 10k requests.
Concurrency vs Parallelism
Concurrency — multiple tasks making progress by interleaving. One core switching between tasks. "Dealing with many things at once."
Parallelism — multiple tasks executing simultaneously on multiple cores. True simultaneous execution. "Doing many things at once."
Node.js: Single-threaded, event-loop-based. Concurrency via async I/O, not parallelism. Great for I/O-bound tasks (APIs, DB queries). Bad for CPU-bound tasks (image processing, crypto).
Go: Goroutines — lightweight threads. True parallelism on multiple cores. Excellent for both I/O-bound and CPU-bound.
Python: GIL prevents true thread parallelism. Use multiprocessing or asyncio (single-threaded async like Node.js) depending on task type.
Event Loop (Node.js)
Node.js runs JavaScript on a single thread. The event loop is what makes async I/O possible.
When you await db.query(), Node.js:
1. Registers the async operation with the OS (via libuv)
2. Releases the thread to handle OTHER requests
3. When the OS signals "query done", the event loop picks it up
4. Continues executing after the await
This is why Node.js handles 10,000 concurrent connections with one thread — it's never actually blocked, just waiting for I/O.
Danger: Never block the event loop. CPU-heavy code (JSON.parse of 100MB, crypto, image manipulation) blocks ALL requests. Offload to worker_threads or child_process.
Use setImmediate() or process.nextTick() to defer work to the next iteration of the event loop.
Race Conditions
A race condition is when the outcome depends on the timing of concurrent operations. Classic example:
// User has $100. Two simultaneous withdrawal requests for $80.
// Both read balance: $100
// Both check: 100 >= 80 ✓ (both pass)
// Both write: balance = 100 - 80 = $20
// Result: balance is $20, but $160 was withdrawn. Problem!
Solutions:
1. Database transactions with SELECT FOR UPDATE (row-level locking)
2. Optimistic locking: version column. Update WHERE id=1 AND version=5. If 0 rows updated → retry.
3. Redis INCR/DECR: atomic operations that can't race.
4. Distributed locks: Redis SETNX / Redlock algorithm for cross-service locking.
Rule: Any shared mutable state accessed by concurrent operations needs protection.
The Backend from First Principles series is based on what I learnt from Sriniously's YouTube playlist — a thoughtful, framework-agnostic walk through backend engineering. If this material helped you, please go check the original out: youtube.com/@Sriniously. The notes here are my own restatement for revisiting later.