INTERVIEW_QUESTIONS

Distributed Locking Interview Questions for Senior Engineers (2026)

Top distributed locking interview questions with detailed answer frameworks covering lock algorithms, consensus protocols, fencing tokens, lease-based locks, and real-world distributed coordination patterns for senior engineering interviews.

20 min readUpdated Apr 21, 2026
interview-questionsdistributed-lockingsenior-engineerdistributed-systemsconsensuscoordination

Why Distributed Locking Matters in Senior Engineering Interviews

Distributed locking is one of the most nuanced topics in distributed systems engineering, and it appears frequently in senior and staff-level interviews at companies that operate large-scale infrastructure. Unlike single-process locking where a mutex or semaphore provides straightforward mutual exclusion, distributed locking must contend with partial failures, network partitions, clock skew, and the fundamental impossibility results that govern distributed systems.

Interviewers use distributed locking questions to assess several capabilities simultaneously. They want to see that you understand why distributed locking is fundamentally harder than local locking, that you can reason about failure modes that do not exist in single-machine systems, and that you know when a distributed lock is the right tool versus when alternative coordination patterns are more appropriate. A strong answer demonstrates awareness of the CAP theorem trade-offs, practical experience with locking implementations, and the ability to design systems that remain correct even when the lock itself fails.

The questions in this guide cover the full spectrum from foundational concepts to production-grade implementation details. Each includes the interviewer's intent, a structured answer framework, and follow-up questions that probe deeper. For broader distributed systems preparation, see our distributed systems guide, and for general interview strategy, consult our system design interview guide. Explore our learning paths for a structured study plan.

1. What is a distributed lock, and why is it fundamentally different from a local lock?

What the interviewer is really asking: Do you understand the theoretical and practical challenges that make distributed locking an order of magnitude harder than single-process locking?

Answer framework:

A local lock (mutex, semaphore) provides mutual exclusion within a single process or machine. It relies on guarantees provided by the operating system and hardware: atomic compare-and-swap instructions, shared memory visibility, and the assumption that if the process holding the lock crashes, the OS can release it. These guarantees do not exist in a distributed system.

A distributed lock provides mutual exclusion across multiple processes running on different machines connected by a network. The fundamental challenges include the following.

Partial failure: in a local system, either the whole machine is working or it is not. In a distributed system, the lock service might be up but unreachable from the client, or the client might crash while holding the lock without the lock service knowing. There is no reliable way to distinguish a crashed node from a slow node or a network partition.

No shared memory: processes on different machines cannot use atomic CPU instructions to coordinate. They must communicate through messages, which can be delayed, duplicated, reordered, or lost.

No global clock: without a perfectly synchronized clock, you cannot reliably determine the order of events across machines. This makes timeout-based lock expiration inherently imprecise. A client might believe its lock is still valid while the lock service has already expired it due to clock skew.

The FLP impossibility result tells us that in an asynchronous system (where message delivery time is unbounded), no deterministic consensus algorithm can guarantee both safety and liveness. This means every distributed lock implementation must make a trade-off: either it can potentially deadlock (sacrifice liveness) or it can potentially allow two clients to hold the lock simultaneously (sacrifice safety).

In practice, distributed locks use timeouts and leases to provide eventual liveness at the cost of requiring careful reasoning about safety during edge cases. The critical insight for senior engineers is that a distributed lock alone is often insufficient. You need additional mechanisms like fencing tokens to ensure correctness when the lock's safety guarantee is violated.

Follow-up questions:

  • Can you describe a scenario where a distributed lock's mutual exclusion guarantee is violated?
  • What does the FLP impossibility result imply for the design of distributed locks?
  • When would you choose not to use a distributed lock and use a different coordination mechanism instead?

2. How does Redlock work, and what are its limitations?

What the interviewer is really asking: Do you understand the most debated distributed lock algorithm, and can you articulate both sides of the Redlock controversy?

Answer framework:

Redlock is a distributed lock algorithm proposed by Redis creator Salvatore Sanfilippo. It uses N independent Redis instances (typically 5) to provide a lock that survives the failure of a minority of nodes.

The algorithm works as follows. The client records the current time T1. It sequentially attempts to acquire the lock on all N Redis instances using SET with NX (only if not exists) and PX (expiration in milliseconds). Each acquisition attempt has a small timeout (much smaller than the lock TTL) to avoid blocking on unresponsive nodes. If the client successfully acquires the lock on a majority (N/2 + 1 or more) of instances, and the total elapsed time (T2 - T1) is less than the lock TTL, the lock is considered acquired. The effective lock validity time is TTL minus the elapsed time. If the client fails to acquire the majority, it releases the lock on all instances it did acquire.

The limitations and controversy, famously articulated by Martin Kleppmann, center on several issues.

Clock dependency: Redlock assumes that the clocks on Redis instances advance at roughly the same rate. If a Redis instance's clock jumps forward (due to NTP adjustment, VM migration, or a leap second), it might expire the lock prematurely while the client believes it still holds it. This violates mutual exclusion.

Process pause problem: after acquiring the lock, the client might experience a long GC pause, a page fault, or a context switch. By the time the client resumes, the lock may have expired, but the client does not know this. It proceeds to access the shared resource, potentially conflicting with another client that has since acquired the lock.

No fencing mechanism: Redlock itself does not provide a fencing token, a monotonically increasing value that the shared resource can use to reject stale operations. Without fencing, even if mutual exclusion is momentarily violated, there is no way for the resource to detect and reject the stale lock holder's operations.

The counter-argument from Sanfilippo is that Redlock provides better guarantees than a single-node Redis lock and is sufficient for use cases that can tolerate occasional safety violations. The key insight for interviews is knowing when Redlock is appropriate (efficiency-based locking where double-processing is merely wasteful but not catastrophic) versus when it is insufficient (correctness-based locking where double-processing causes data corruption).

Follow-up questions:

  • How does a single-node Redis lock compare to Redlock in terms of safety guarantees?
  • What would you use instead of Redlock when you need strict correctness?
  • How does the clock jump problem affect Redlock, and can it be mitigated?

3. Explain fencing tokens and why they are critical for distributed lock correctness.

What the interviewer is really asking: Do you understand that the lock itself is not sufficient for correctness, and can you explain the mechanism that provides end-to-end safety?

Answer framework:

A fencing token is a monotonically increasing number issued by the lock service each time a lock is acquired. The client includes the fencing token in every request to the shared resource. The resource tracks the highest fencing token it has seen and rejects any request with a lower token.

Why this is necessary: consider the following scenario without fencing. Client A acquires the lock with a 10-second TTL. Client A experiences a long GC pause lasting 15 seconds. The lock expires after 10 seconds. Client B acquires the lock. Client B writes to the shared resource. Client A resumes from GC, believes it still holds the lock, and writes to the shared resource, overwriting Client B's write. Mutual exclusion has been violated, and data is corrupted.

With fencing tokens: Client A acquires the lock and receives fencing token 33. Client A pauses for 15 seconds. The lock expires. Client B acquires the lock and receives fencing token 34. Client B writes to the resource with token 34. The resource records 34 as the highest token. Client A resumes and tries to write with token 33. The resource rejects the write because 33 is less than the recorded high watermark of 34. Data integrity is preserved.

The critical insight is that fencing tokens shift the safety guarantee from the lock service to the resource being protected. The lock service provides best-effort mutual exclusion and a monotonically increasing token. The resource enforces the hard safety guarantee by rejecting stale tokens. This works even if the lock's mutual exclusion guarantee is temporarily violated.

Implementation considerations: the resource must support conditional writes based on the fencing token. For databases, this can be a version column checked in every UPDATE WHERE clause. For file systems, it can be a generation number. For distributed storage systems like ZooKeeper, the zxid (transaction ID) naturally serves as a fencing token. Not all resources support fencing natively, which limits the applicability of this pattern.

The leader election pattern is closely related: a leader holds a lease with an epoch number (equivalent to a fencing token), and followers reject commands from leaders with stale epochs.

Follow-up questions:

  • How would you implement fencing for a resource that does not natively support conditional writes?
  • What is the relationship between fencing tokens and optimistic concurrency control?
  • Can fencing tokens be implemented on top of Redlock?

4. How would you implement a distributed lock using ZooKeeper?

What the interviewer is really asking: Do you understand consensus-based locking and can you explain the specific ZooKeeper primitives that make it reliable?

Answer framework:

ZooKeeper provides a much stronger foundation for distributed locks than Redis because it uses the ZAB (ZooKeeper Atomic Broadcast) consensus protocol, which guarantees linearizable writes and sequential consistency for reads. This means ZooKeeper can provide fencing tokens natively.

The standard lock recipe uses ephemeral sequential znodes. To acquire the lock: create an ephemeral sequential znode under a designated lock path, for example /locks/my-resource/lock-. ZooKeeper appends a monotonically increasing sequence number, creating /locks/my-resource/lock-0000000001. Get all children of /locks/my-resource/. If this client's znode has the lowest sequence number, the lock is acquired. The sequence number serves as the fencing token. If the client's znode is not the lowest, set a watch on the znode with the next lower sequence number (not on all children, to avoid the herd effect). When the watched znode is deleted (lock released or holder crashed), re-check if this client's znode is now the lowest.

The ephemeral node property is crucial: if the lock holder's session expires (due to crash or network partition), ZooKeeper automatically deletes the ephemeral node, releasing the lock. This avoids the indefinite lock holding problem. The session timeout is configurable (typically 10-30 seconds) and is based on heartbeats between the client and ZooKeeper, not wall-clock time.

Advantages over Redlock: ZooKeeper provides linearizable operations backed by consensus, so there is no clock dependency. Ephemeral nodes handle lock holder crashes automatically. The sequential znode number is a natural fencing token. Watches provide efficient notification without polling.

Disadvantages: ZooKeeper is operationally complex to run. Write throughput is limited by the consensus protocol (typically thousands per second, not millions). The session timeout introduces latency in failure detection. ZooKeeper is a CP system under the CAP theorem, meaning it sacrifices availability during network partitions.

Alternatives in the same category include etcd (uses Raft consensus, provides similar guarantees with a simpler operational model) and Consul (provides distributed locking with session-based semantics similar to ZooKeeper).

Follow-up questions:

  • What happens if there is a network partition between the lock holder and ZooKeeper?
  • How does the herd effect problem manifest in a naive ZooKeeper lock implementation?
  • How would you choose between ZooKeeper and etcd for distributed locking?

5. What is the difference between a lease and a lock in distributed systems?

What the interviewer is really asking: Do you understand time-bounded ownership and why leases are the practical foundation of almost all distributed coordination?

Answer framework:

A lock in the traditional sense grants indefinite exclusive access until explicitly released. A lease is a lock with an expiration time. The distinction matters enormously in distributed systems because indefinite locks combined with the possibility of holder crashes create the risk of permanent deadlock.

A lease grants ownership for a bounded duration. If the holder crashes or becomes unreachable, the lease eventually expires, and another client can acquire it. This provides a strong liveness guarantee: the system will always make progress, even in the presence of failures. The trade-off is that the holder must continuously renew the lease before it expires, and there is a window of vulnerability if the holder's operation takes longer than the lease duration.

Lease mechanics involve three parameters: the grant duration (how long the lease is valid), the renewal interval (how often the holder should renew, typically at half the grant duration), and the grace period (additional time after expiration before another client can acquire, optional). The holder must complete its work and release the lease, or renew it, before expiration. If the holder cannot renew (due to network issues or load), it must assume the lease is lost and stop accessing the protected resource.

The fundamental tension in lease design is the duration. A short lease (1-5 seconds) provides fast failover when the holder crashes but requires frequent renewals and is sensitive to transient network hiccups. A long lease (30-60 seconds) is more tolerant of transient issues but delays failover. In practice, lease durations of 10-30 seconds with renewal at half the interval provide a reasonable balance.

Real-world lease usage extends far beyond mutual exclusion. Leader election in systems like Kafka and Elasticsearch uses leases to maintain leadership. DHCP uses leases for IP address allocation. Distributed storage systems use leases for cache coherence. Chubby (Google's lock service) is fundamentally a lease manager that provides a file-system-like interface for acquiring and renewing leases.

The relationship to leader election is direct: a leader holds a lease. When the lease expires, a new election occurs. This ensures that at most one leader exists at any time (safety) and that a new leader will eventually be elected if the current one fails (liveness).

Follow-up questions:

  • How do you handle the case where a lease holder's operation takes longer than the lease duration?
  • What is the impact of clock skew on lease-based systems?
  • How does Google's Chubby service use leases, and what lessons does its design teach?

6. How would you handle the problem of long GC pauses with distributed locks?

What the interviewer is really asking: Do you understand one of the most insidious failure modes in distributed locking, and do you know practical mitigation strategies?

Answer framework:

Garbage collection pauses in languages like Java, Go, and C# can cause a lock holder to stop executing for milliseconds to seconds. During this pause, the lock's lease may expire without the holder knowing. When the holder resumes, it incorrectly believes it still holds the lock and proceeds to modify the protected resource, potentially conflicting with the new lock holder.

This is not a theoretical concern. Production systems at major companies have experienced data corruption due to this exact scenario. A 15-second GC pause is uncommon but not impossible, especially in JVM-based systems with large heaps.

Mitigation strategies operate at multiple levels. First, use fencing tokens as described in question 3. This is the only approach that provides true correctness. The resource rejects operations from a stale lock holder regardless of why the holder was delayed.

Second, implement lock validation before critical operations. After acquiring the lock, check its validity immediately before each critical write operation (not just once at the beginning). In ZooKeeper, this means checking that the session is still alive and the ephemeral node still exists. This narrows the vulnerability window but does not eliminate it since the lock could expire between the check and the write.

Third, use cooperative lease checking. The lock client library monitors the remaining lease duration and exposes a method like isStillValid() that returns false if less than a safety margin remains. The application code checks this before proceeding with each step. Configure the safety margin to be larger than the worst-case GC pause (for example, if the worst-case GC pause is 5 seconds, use a safety margin of 10 seconds).

Fourth, minimize GC pause duration at the infrastructure level. Use low-latency garbage collectors (ZGC, Shenandoah for JVM), tune heap sizes to reduce full GC frequency, use off-heap memory for large data structures, and consider languages without GC (Rust, C++) for lock-sensitive components.

Fifth, use a watchdog thread that runs in a separate process (not subject to the same GC pauses) and monitors the lock holder. If the watchdog detects that the holder has paused, it can proactively release the lock and prevent the holder from continuing when it resumes.

Follow-up questions:

  • Can the lock validation approach guarantee safety, or does it only reduce the probability of violation?
  • How would you design a test to verify that your system handles GC pauses correctly?
  • What other types of process pauses besides GC can cause similar issues?

7. Explain the Raft consensus algorithm and its role in distributed locking.

What the interviewer is really asking: Do you understand the consensus foundation that powers modern distributed lock services like etcd, and can you explain it clearly?

Answer framework:

Raft is a consensus algorithm designed to be more understandable than Paxos while providing the same guarantees. It ensures that a cluster of nodes agrees on a sequence of operations even when a minority of nodes fail. This property is exactly what a correct distributed lock service needs.

Raft has three roles: leader, follower, and candidate. At any time, at most one leader exists. The leader handles all client requests and replicates them to followers. Safety is guaranteed as long as a majority (N/2 + 1) of nodes are operational.

The leader election process works through terms (monotonically increasing epoch numbers). When a follower does not receive a heartbeat from the leader within the election timeout, it becomes a candidate, increments its term, votes for itself, and requests votes from other nodes. A node votes for at most one candidate per term. A candidate wins if it receives votes from a majority. If multiple candidates split the vote, a new election starts with a higher term after a randomized timeout.

Log replication: the leader appends client operations to its log and replicates them to followers. An entry is committed when a majority of nodes have stored it. Committed entries are guaranteed to be present in any future leader's log. This is the foundation of linearizable operations.

For distributed locking, services like etcd implement a lock on top of Raft. When a client requests a lock, the leader writes the lock acquisition to the Raft log. Once committed (majority acknowledgment), the lock is durably held. If the leader fails, the new leader's log contains the lock state, so the lock is preserved. Because Raft provides linearizable writes, there is no window where two clients can simultaneously believe they hold the lock (as long as the lock service itself is correct).

The compare-and-swap (CAS) operation is the key primitive for locking on etcd: "set this key to my value only if it does not currently exist or has the expected revision." Raft ensures this CAS is executed atomically and durably.

Compare with leader election: Raft's leader election is itself a form of distributed locking where only one node can be the leader at a time. This is why Raft-based systems are natural choices for implementing distributed locks.

Follow-up questions:

  • What happens to distributed locks during a Raft leader election?
  • How does Raft handle network partitions, and what does this mean for lock availability?
  • What is the performance overhead of Raft-based locking compared to Redis-based locking?

8. How would you implement distributed rate limiting using distributed locks?

What the interviewer is really asking: Can you apply distributed locking to a practical problem while recognizing when a lock is the right tool and when alternative approaches are better?

Answer framework:

Distributed rate limiting ensures that a global rate limit is enforced across multiple server instances. For example, an API might allow 1,000 requests per minute per API key, but requests are handled by any of 50 servers.

The naive approach uses a distributed lock: acquire a lock, read the current counter from a shared store, increment it, check if the limit is exceeded, write the updated counter, and release the lock. This works correctly but has severe performance problems. Every rate-limited request requires acquiring and releasing a distributed lock, adding latency (milliseconds per lock operation). The lock becomes a bottleneck since all traffic for a given API key is serialized through the lock. If the lock service is slow or unavailable, all rate-limited requests are blocked.

A better approach eliminates the lock entirely using atomic operations. Use Redis INCR with EXPIRE for a simple sliding window counter. INCR is atomic in Redis, so concurrent increments are safe without a lock. The key is the API key plus the current time window (for example, apikey:123:minute:2026-04-21T14:30). INCR atomically increments and returns the new value. If the value exceeds the limit, reject the request. If this is the first increment, set an expiration on the key.

For the token bucket algorithm in a distributed setting, use Redis with a Lua script that atomically refills tokens based on elapsed time and attempts to consume a token. The Lua script runs atomically in Redis, providing the mutual exclusion needed without an external lock.

For even higher throughput, use local rate limiting with periodic synchronization. Each server maintains a local counter and a local token allocation. Periodically (every few seconds), servers synchronize with a central store to redistribute tokens. This sacrifices precision (the global limit might be slightly exceeded) but dramatically improves throughput and eliminates the central bottleneck. This is the approach companies like Google and Stripe use for high-volume API rate limiting.

The lesson for distributed locking interviews: a distributed lock is a tool of last resort for coordination. Before reaching for a lock, ask whether the problem can be solved with atomic operations, optimistic concurrency control, or eventual consistency.

Follow-up questions:

  • What are the failure modes of Redis-based rate limiting when a Redis node goes down?
  • How would you rate limit across multiple data centers with independent Redis clusters?
  • When is a distributed lock genuinely necessary for rate limiting?

9. How do you prevent deadlocks in a system with multiple distributed locks?

What the interviewer is really asking: Do you understand that distributed deadlock is harder to detect and resolve than local deadlock, and do you know practical prevention strategies?

Answer framework:

Deadlock occurs when two or more processes each hold a lock and are waiting for a lock held by another. The classic conditions are mutual exclusion, hold and wait, no preemption, and circular wait. In a distributed system, deadlock detection is harder because no single node has visibility into all lock-wait relationships.

Prevention strategy one: lock ordering. Define a global total order on all lockable resources (for example, alphabetical by resource name). All clients must acquire locks in this order. This prevents circular wait, eliminating the possibility of deadlock. This is the simplest and most reliable approach. Example: if a transaction needs locks on accounts A and B, always lock A first regardless of which account is the source and which is the destination.

Prevention strategy two: timeout-based acquisition. When attempting to acquire a lock, set a timeout. If the lock is not acquired within the timeout, release all currently held locks and retry (possibly with exponential backoff and jitter to prevent livelock). This breaks the hold-and-wait condition. The trade-off is that transactions may be aborted and retried, reducing throughput under contention.

Prevention strategy three: try-lock with rollback. Attempt to acquire all needed locks without blocking. If any lock acquisition fails, release all acquired locks and retry. This is similar to optimistic locking: assume no conflict, back off if there is one. Works well when contention is low.

Detection and resolution: if prevention is not feasible, implement deadlock detection. Build a wait-for graph by periodically collecting which client is waiting for which lock from the lock service. Detect cycles in the graph. When a cycle is found, choose a victim (typically the client that has done the least work) and forcibly revoke its locks. In a distributed system, constructing the wait-for graph requires coordinating information from multiple lock servers, which introduces latency in detection.

For practical systems, prefer prevention over detection. Lock ordering combined with timeouts handles the vast majority of cases. Reserve deadlock detection for systems where lock ordering is impractical due to dynamic, unpredictable access patterns.

Follow-up questions:

  • How does distributed deadlock detection differ from local deadlock detection?
  • What is the risk of livelock when using timeout-based deadlock prevention?
  • How would you implement lock ordering in a system where the set of required locks is not known in advance?

10. How does Google's Chubby lock service work?

What the interviewer is really asking: Do you understand the design of a production-grade distributed lock service and the lessons learned from operating it at scale?

Answer framework:

Chubby is Google's distributed lock service, described in the 2006 paper by Mike Burrows. It provides coarse-grained locking and a small file storage system for loosely-coupled distributed systems. Chubby is not a general-purpose lock service. It is designed for locks held for hours or days (leader election, master selection), not milliseconds.

Architecture: a Chubby cell consists of 5 replicas, one of which is elected master using Paxos consensus. The master handles all client requests. Replicas maintain a replicated database that stores the lock state. If the master fails, a new master is elected, and the replicated database ensures continuity.

Chubby exposes a file-system-like interface. Locks are represented as files or directories. Acquiring a lock means opening a file with exclusive access. The contents of the file can store metadata (for example, the current leader's address). This dual-purpose design (lock plus small data store) is what makes Chubby so useful for leader election: the leader acquires the lock and writes its address to the file, and followers read the file to discover the leader.

Sessions and leases: each client maintains a session with the Chubby cell via periodic KeepAlive RPCs. The session has a lease that the master extends on each KeepAlive. If the client fails to renew within the timeout, the session expires and all locks held by that session are released (similar to ZooKeeper's ephemeral nodes).

Sequencers (fencing tokens): when a client acquires a lock, it can request a sequencer, an opaque byte string containing the lock name, mode, and lock generation number. The client passes the sequencer to servers that should verify the lock is still held. This provides the fencing token mechanism critical for correctness.

Key design lessons from Chubby: developers often misused Chubby for fine-grained locking, causing performance problems. The service was designed for coarse-grained locks held for long periods. Chubby included a mechanism to detect and break locks held by unresponsive clients, but this was controversial because it violated the expectation of mutual exclusion. The solution was sequencers, which provide safety even when locks are broken.

Follow-up questions:

  • Why did Google choose coarse-grained locking for Chubby instead of fine-grained locking?
  • How does Chubby handle the case where the master fails during a lock acquisition?
  • What are the similarities and differences between Chubby and ZooKeeper?

11. How would you implement optimistic locking in a distributed database?

What the interviewer is really asking: Do you understand lock-free coordination and when optimistic concurrency control is preferable to distributed locks?

Answer framework:

Optimistic locking is not truly a lock at all. It is a concurrency control strategy based on the assumption that conflicts are rare. Instead of acquiring a lock before reading, the client reads data along with a version number, performs its computation, and then writes the result only if the version has not changed. If the version has changed (indicating a concurrent modification), the write is rejected and the client retries.

Implementation in a relational database: add a version column to each row. A read returns the current version. An update includes WHERE version = expected_version and sets version = version + 1. If the WHERE clause matches zero rows, the update failed due to a concurrent modification. The client must re-read the current state and retry its operation.

Implementation in a distributed key-value store: etcd's transactions support compare-and-swap on the revision number. DynamoDB's conditional expressions support attribute_not_exists or attribute_equals for optimistic locking. CockroachDB and Spanner use timestamp-based optimistic concurrency control as part of their MVCC (Multi-Version Concurrency Control) implementation.

Advantages over distributed locks: no lock service dependency (one fewer infrastructure component), no risk of deadlock (there are no locks to deadlock on), higher throughput under low contention (no lock acquisition overhead), and simpler failure handling (if a client crashes mid-operation, no lock needs to be released).

Disadvantages: under high contention, optimistic locking causes excessive retries, wasting resources and increasing latency. The retry logic must be carefully implemented with exponential backoff and jitter to prevent livelock. Long-running transactions are particularly problematic because they are more likely to conflict, and retrying a long transaction is expensive.

The decision framework: use optimistic locking when conflicts are rare (less than 5 percent of operations conflict), transactions are short, and retry overhead is acceptable. Use distributed locks (pessimistic locking) when conflicts are frequent, transactions are long or expensive, or the cost of a failed transaction is high (for example, external side effects that cannot be retried).

In practice, many systems use a hybrid approach. Use optimistic locking for the common case and fall back to a distributed lock under high contention (for example, after three failed optimistic attempts, acquire a lock).

Follow-up questions:

  • How do you handle the ABA problem in optimistic locking?
  • What is the relationship between MVCC and optimistic locking?
  • How would you implement optimistic locking across multiple tables in a single transaction?

12. How do distributed databases like Spanner achieve external consistency without traditional locking?

What the interviewer is really asking: Do you understand the most sophisticated approach to distributed coordination and how TrueTime enables lock-free serializable transactions?

Answer framework:

Google Spanner achieves external consistency (also called strict serializability or linearizability of transactions) using a combination of two-phase locking for write transactions and TrueTime for assigning globally meaningful timestamps.

TrueTime is a clock API that returns a time interval [earliest, latest] rather than a single timestamp. It guarantees that the actual current time falls within this interval. The interval width is typically 1-7 milliseconds, kept tight by GPS receivers and atomic clocks in Google's data centers. This is fundamentally different from NTP, which provides a single timestamp with unbounded error.

For read-write transactions, Spanner uses traditional two-phase locking: the transaction acquires read locks and write locks on the data it accesses. At commit time, the transaction is assigned a commit timestamp using TrueTime. The key insight: the transaction leader waits until it is certain that the commit timestamp is in the past (waits for the TrueTime uncertainty interval to pass). This wait, called commit-wait, ensures that any transaction that starts after this one commits will see this transaction's writes. This provides external consistency without requiring a global lock manager.

For read-only transactions, Spanner uses snapshot reads at a timestamp. Because committed transactions have globally meaningful timestamps and Spanner's MVCC retains multiple versions, a read at timestamp T sees all transactions committed before T and none committed after T. Read-only transactions require no locks at all, which is critical for read-heavy workloads.

This relates to distributed locking because Spanner shows that with sufficiently accurate clocks, you can reduce the need for distributed coordination. The commit-wait delay (a few milliseconds) replaces what would otherwise require a distributed consensus round for ordering. This is a profound trade-off: invest in clock infrastructure (GPS, atomic clocks) to simplify the distributed coordination protocol.

The lesson for broader distributed locking design: the quality of your time source determines what coordination shortcuts are available. With NTP (millisecond to second accuracy), you cannot rely on timestamps for ordering. With TrueTime (microsecond accuracy with bounded error), you can use timestamps with commit-wait. With perfect clocks (impossible), you would not need distributed coordination at all.

For related concepts, see CAP theorem to understand the theoretical constraints that Spanner navigates.

Follow-up questions:

  • Why cannot NTP provide the same guarantees as TrueTime?
  • What happens to Spanner's consistency guarantees if the TrueTime interval becomes very wide?
  • How does CockroachDB approximate Spanner's approach without specialized clock hardware?

13. How would you design a distributed lock service that supports lock queuing and fairness?

What the interviewer is really asking: Can you extend the basic distributed lock model to handle priority, ordering, and starvation prevention?

Answer framework:

A basic distributed lock is often a try-lock: either you get it or you do not. For many systems, this is insufficient. You need queuing so that waiters are served in order, fairness so that no client starves, and potentially priority so that urgent operations can jump the queue.

For FIFO fairness (first come, first served), use the ZooKeeper-style approach with sequential nodes. Each client creates a sequential node. The client with the lowest sequence number holds the lock. Others are queued in sequence number order. When the lock holder releases, the next in line is notified. This provides strict FIFO ordering and prevents starvation since every client eventually reaches the front of the queue.

For priority-based queuing, extend the model with priority levels. Maintain separate queues per priority level. A high-priority client jumps ahead of lower-priority clients but is still ordered within its priority level. Implementation: use the sequential node approach but prefix the node name with the priority level. When selecting the next lock holder, choose the node with the highest priority, breaking ties by sequence number. This risks starvation of low-priority clients, so implement a priority aging mechanism: if a client has been waiting longer than a threshold, temporarily boost its priority.

For read-write lock semantics, support shared (read) and exclusive (write) locks. Multiple readers can hold the lock simultaneously, but a writer requires exclusive access. Implementation: use sequential nodes with a type prefix (R for read, W for write). A reader can proceed if all earlier nodes are readers. A writer can proceed only when it is the first node in the queue. This provides concurrent read access while ensuring write exclusion.

For scalability, the lock queue should not become a bottleneck. If thousands of clients are queuing for the same lock, the notification overhead when the lock is released could be significant. Use the watch-predecessor pattern (as in ZooKeeper) where each client watches only the node immediately ahead of it, providing O(1) notification cost per lock release.

For timeout-based queue management, allow clients to specify a maximum wait time. If the lock is not acquired within the timeout, the client removes its queue entry and receives a failure. This prevents unbounded queue growth and allows clients to take alternative action when contention is high.

Follow-up questions:

  • How would you implement a try-lock-with-timeout on top of a queuing lock?
  • What is the thundering herd problem in lock queuing, and how do you prevent it?
  • How do you handle the case where a queued client crashes before its turn comes?

14. What are the alternatives to distributed locks for coordinating distributed systems?

What the interviewer is really asking: Do you understand the full toolkit for distributed coordination and know when not to use a lock?

Answer framework:

Distributed locks are one coordination mechanism among many. Senior engineers should know the alternatives and choose the most appropriate tool for each situation.

Idempotent operations: design operations so that executing them multiple times produces the same result as executing once. If operations are idempotent, you do not need mutual exclusion because concurrent or duplicate executions are harmless. Example: SET key=value is idempotent, INCREMENT key is not. Designing for idempotency often eliminates the need for distributed locks entirely.

Optimistic concurrency control: as discussed in question 11, use version numbers and conditional writes to detect conflicts without locks. Suitable for low-contention scenarios.

Event sourcing and CQRS: instead of coordinating access to mutable shared state, model the system as an append-only log of events. Each event is immutable and ordered. Consumers process events sequentially. Since there is no shared mutable state, there is nothing to lock. Kafka-based architectures often follow this pattern.

Single-writer principle: assign each piece of data to a single writer (owner). Only the owner can modify the data. This eliminates write contention by design. If the owner fails, a new owner is elected using leader election. The trade-off is that all writes for a given piece of data must route to the same node, which can be a bottleneck.

CRDTs (Conflict-free Replicated Data Types): data structures that can be updated independently on different nodes and merged automatically without conflicts. Counters, sets, and registers all have CRDT variants. CRDTs trade coordination for convergence since they always converge to the same state without any locking or consensus.

Partitioning: divide the data space so that each operation touches only one partition. Within a partition, use a local lock or single-threaded processing. This is how databases like Kafka achieve high throughput. Each partition is processed by a single consumer, eliminating the need for distributed coordination.

The decision framework for senior engineers: start by asking whether coordination can be avoided entirely (idempotency, CRDTs, partitioning). If coordination is necessary, prefer optimistic approaches (conditional writes, version checks). Use distributed locks only when pessimistic coordination is required and the failure mode of the lock is acceptable for your use case.

Follow-up questions:

  • How would you choose between a distributed lock and an event-sourced architecture for a specific problem?
  • Can CRDTs replace distributed locks for all use cases?
  • How does the single-writer principle relate to Kafka's consumer group model?

15. How would you test and validate a distributed locking implementation?

What the interviewer is really asking: Do you know how to verify correctness of distributed systems, including testing failure modes that are difficult to reproduce?

Answer framework:

Testing distributed locks is uniquely challenging because the most important bugs manifest only under specific timing conditions: network delays, clock skew, process pauses, and partial failures. Standard unit and integration tests are necessary but insufficient.

Correctness testing with Jepsen: Jepsen is a framework designed specifically for testing distributed systems. It runs operations against the system while injecting faults (network partitions, node crashes, clock skew) and verifies that the system maintains its safety guarantees. For a distributed lock, the key property to verify is mutual exclusion: at no point should two clients simultaneously believe they hold the same lock. Run thousands of test iterations with randomized fault injection to build confidence.

Linearizability checking: record a history of lock operations (acquire, release) with their real-time ordering. Use a linearizability checker (like the one in Jepsen or the Knossos checker) to verify that the observed history is consistent with a sequential specification. If the checker finds a non-linearizable history, you have a correctness bug.

Fault injection testing: systematically test each failure mode. Network partition between client and lock service: does the client correctly detect that it lost the lock? Process pause injection (using SIGSTOP to simulate GC pauses): does the fencing token mechanism correctly prevent stale operations? Clock skew injection (using libfaketime): does the lock expire correctly under clock drift? Lock service node failure: does the lock service correctly hand off to a new leader without violating mutual exclusion?

Performance testing under contention: measure lock acquisition latency and throughput as you increase the number of competing clients. Identify the contention level at which performance degrades unacceptably. Test the queue behavior: with 1,000 clients waiting for the same lock, how long does the average client wait? Is the wait-time distribution fair?

Chaos engineering in production: once the implementation is deployed, run controlled chaos experiments. Use tools like Chaos Monkey to randomly terminate lock service nodes. Use traffic control (tc) to inject latency and packet loss between clients and the lock service. Monitor the safety property (no concurrent holders) and the liveness property (locks are always eventually acquired after release) during these experiments.

Observability: instrument the lock service with metrics including lock acquisition latency (p50, p99), lock hold duration, contention rate (percentage of acquisitions that must wait), timeout rate (percentage of acquisitions that time out), and fencing token rejection rate at the resource. Alert on anomalies in these metrics, which may indicate correctness issues before they cause data corruption.

For a broader perspective on testing distributed systems, see our distributed systems guide.

Follow-up questions:

  • How would you set up a continuous integration pipeline that tests distributed lock correctness on every commit?
  • What is the value of formal verification methods like TLA+ for distributed lock design?
  • How do you test the interaction between your lock implementation and GC pauses in a controlled way?

Common Mistakes in Distributed Locking Interviews

  1. Using a distributed lock when it is not needed. The most common mistake is reaching for a lock when the problem can be solved with idempotent operations, optimistic concurrency control, or partitioning. Always explain why a lock is necessary before proposing one. Interviewers value candidates who can identify simpler alternatives.

  2. Ignoring what happens when the lock fails. Every distributed lock can fail: the lock service can go down, the holder can crash, or the network can partition. A complete answer always addresses the failure modes and explains how the system remains correct (or degrades gracefully) when the lock's guarantees are violated.

  3. Treating Redis as a reliable lock service. Redis provides at-most-once delivery and does not use consensus replication by default. A lock acquired on a Redis primary can be lost if the primary crashes before replicating to a replica. Acknowledge this limitation and explain when Redis-based locking is acceptable versus when consensus-based locking (etcd, ZooKeeper) is required.

  4. Forgetting about fencing tokens. Proposing a distributed lock without a fencing mechanism leaves a critical safety gap. Always explain how the protected resource verifies that the lock holder is current, not stale.

  5. Not considering the CAP theorem implications. A distributed lock service must choose between consistency and availability during network partitions. A CP lock service (ZooKeeper, etcd) becomes unavailable during partitions. An AP lock service could allow multiple holders during partitions. Explain which trade-off your design makes and why it is appropriate for the use case. Review the CAP theorem for a deeper understanding.

How to Prepare for Distributed Locking Interviews

Build your understanding from first principles over 3-4 weeks. Start with the foundational impossibility results: the FLP impossibility theorem, the CAP theorem, and the two generals problem. These explain why distributed coordination is fundamentally hard and set the context for all locking discussions.

Study the major consensus algorithms: Paxos (conceptual understanding), Raft (detailed understanding, as it underpins etcd), and ZAB (for ZooKeeper). Implement a basic Raft leader election to internalize the mechanics. Understanding consensus is prerequisite to understanding why certain lock implementations are correct and others are not.

Read the primary sources: the Chubby paper (2006), the ZooKeeper paper (2010), the Redlock specification, and Martin Kleppmann's critique of Redlock. These are the most frequently referenced materials in distributed locking interviews.

Build hands-on experience: implement a distributed lock using Redis and deliberately break it by injecting network partitions and process pauses. Then implement the same lock using etcd and observe the difference in behavior under the same failure conditions. This practical experience is invaluable in interviews.

Practice explaining trade-offs clearly. The hallmark of a strong senior candidate is the ability to say: "This approach provides X guarantee at the cost of Y. For our use case, this is acceptable because Z." Interviewers at companies like Google and Stripe specifically look for this structured reasoning.

For comprehensive preparation, explore our learning paths for distributed systems tracks, and see the system design interview guide for broader preparation strategy. Visit our pricing page to explore premium materials for advanced topics.

Related Resources

GO DEEPER

Master this topic in our 12-week cohort

Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.