Under The Hood
backend go Go 1.21+ (sync.Mutex)

sync.Mutex: Normal vs Starvation Mode (Fairness Handoff)

Last updated
Prerequisites:
Go goroutines and mutex basics
Understanding of lock contention
  • go
  • sync
  • mutex
  • starvation
  • fairness
  • concurrency

Read at your depth

The practical view

sync.Mutex is Go's mutual exclusion lock: mu.Lock() / mu.Unlock(). It has two modes: normal mode (default) and starvation mode. In normal mode, a newly-unlocking goroutine hands the lock to a waiting goroutine that was first in the queue (the head), but a NEW goroutine calling Lock() can 'barge' and grab it first if the lock is still free when it arrives — this favors throughput but can let a waiter starve. When a waiter waits longer than ~1ms, the mutex switches to starvation mode: the unlock hands the lock directly to the head waiter (FIFO), and new arrivals queue without barging. After the head waiter gets the lock (or waits <1ms), the mutex returns to normal mode.

The same idea in other frameworks

react equivalent
N/A — Go runtime concern.

Legacy vs modern

Barging normal mode vs starvation-mode fairness

Normal mode lets new arrivals grab a just-released lock (higher throughput, possible starvation); starvation mode enforces FIFO handoff to bound worst-case latency.

before → after
Assuming mutex is strictly FIFO
mu.Lock() // ❌ OLD: assuming 'first come, first served' always
Understanding the two modes
mu.Lock() // ✅ NEW: normal mode (throughput) → starvation mode (fairness)

Interview gotchas

Context

This tests the fairness mechanism — interviewers want the barging vs FIFO handoff distinction and the starvation threshold that drives the transition.

The mechanical answer

In normal mode, a goroutine calling Lock() while the mutex is unlocked can acquire it immediately (barging), even if other goroutines are already waiting — this favors throughput because handoffs are expensive and the lock is used productively. The downside is that a waiting goroutine can be overtaken repeatedly. When a goroutine has been waiting for more than about 1 millisecond, the mutex switches to starvation mode: the unlock operation hands the lock DIRECTLY to the first goroutine in the wait queue (FIFO handoff, no barging), and newly arriving Lock() calls queue behind it. Once the starved goroutine acquires the lock (and its total wait drops below the threshold), the mutex returns to normal mode. This guarantees a maximum wait bound while preserving throughput in the common case.

Trap

The naive answer is 'sync.Mutex is always FIFO'. It's FIFO only in starvation mode; normal mode allows barging. Another trap: thinking you can configure the mode — it's automatic and timing-based, not a knob. Candidates who describe the ~1ms threshold and the automatic transition show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.