Advanced LLD Concurrency and error handling

Concurrency and error handling

Keep shared data correct when things overlap, and fail in ways you can recover from.

  • 15 min read
  • 7 chapters
  • Lesson 1 of 3

What you will learn

  1. 01Why concurrency matters
  2. 02Race conditions
  3. 03Locks
  4. 04Alternatives to locking
  5. 05Errors: expected, unexpected, and bugs
  6. 06Handling errors well
  7. 07Checklist
01

Why concurrency matters

Real systems do many things at once: two gates open together, a payment callback lands during a booking. When overlapping work touches the same data, correctness is no longer about each step but about how steps interleave.

02

Race conditions

A race condition is a bug whose outcome depends on timing. The classic form is check then act.

function reserve(slot: Slot, plate: string) {
  if (slot.free) {          // 1. both cars see free
    slot.free = false;      // 2. both cars take it
    slot.plate = plate;
  }
}

Two cars call this at the same instant, both pass the check before either writes, and one slot is sold twice. It works in every test and fails at 6pm on a busy day.

reservereservein turnCar ACLIENTwants slot 12Car BCLIENTwants slot 12LockLLDone at a timeSlot 12DBfree or taken

Swipe sideways to pan · drag a box to move it

100%
Two cars request the same slot at the same moment.
reservereservein turnCar ACLIENTwants slot 12Car BCLIENTwants slot 12LockLLDone at a timeSlot 12DBfree or taken

Swipe sideways to pan · drag a box to move it

100%
With no protection both read free, both write: double booking.
03

Locks

A lock (mutex) lets one thread at a time run a critical section, the code that touches shared data.

await mutex.runExclusive(() => {
  if (slot.free) { slot.free = false; slot.plate = plate; }
});

Keep critical sections short, since everyone else waits. Locks cost speed and bring a new danger: deadlock, where A holds one lock and waits for B while B holds that one and waits for A. Prevent it by always taking locks in the same order, or by holding only one at a time.

reservereservein turnCar ACLIENTwants slot 12Car BCLIENTwants slot 12LockLLDone at a timeSlot 12DBfree or taken

Swipe sideways to pan · drag a box to move it

100%
A lock lets one request through at a time.
04

Alternatives to locking

  • Immutability: data that never changes needs no lock.
  • Atomic operations: a single indivisible compare and set, supported by hardware and databases.
  • Message passing: one owner thread changes the data and others send it requests through a queue, so there is no sharing.
  • Optimistic concurrency: read a version number, work, and write only if the version is unchanged, otherwise retry. Good when conflicts are rare.
reservereservein turnCar ACLIENTwants slot 12Car BCLIENTwants slot 12LockLLDone at a timeSlot 12DBfree or taken

Swipe sideways to pan · drag a box to move it

100%
The second sees the slot taken and is told no.

A database can do much of this for you. A conditional update such as "set taken where free" is atomic, and a transaction isolates overlapping work.

05

Errors: expected, unexpected, and bugs

Not all failures are equal. Expected failures belong to the domain: payment declined, no free slot. Model them in the return type so callers must handle them. Unexpected failures come from the environment: a network drop, a full disk. A bug is the program being wrong, and it should fail loudly instead of continuing in a corrupt state.

type Result<T> = { ok: true; value: T } | { ok: false; error: string };
06

Handling errors well

  • Fail fast: check preconditions at the door and stop on the first violation.
  • Never swallow: an empty catch hides the problem and moves it somewhere harder to find. Handle it, or let it rise.
  • Add context: say what you were doing and with which id.
  • Clean up: release locks and close resources even on failure, using finally or the language's equivalent.
  • Retry carefully: retry only temporary failures, wait longer between attempts (backoff), cap the attempts, and make the operation idempotent, meaning running it twice has the same effect as once. Charging a card twice because of a retry is the standard disaster.
07

Checklist

  • Find the shared mutable data. Who can touch it at the same time?
  • Prefer removing sharing over protecting it.
  • Keep locks small and ordered.
  • Decide for each error whether to recover, retry, or stop.