Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design)  Module 10 – Consistency, time and coordination

Leader election, leases and fencing tokens

How to elect one leader with a coordination service, why a paused leader can outlive its lease, and how fencing tokens stop its stale writes.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free intro, Pro: 5 of 20 minutes free
Free: A paused leader outlives its lease, so leases alone are not enough. With a pass: the other 7 sections and 3 interview answers, about 15 minutes.

What you will learn

  • Explain how a paused leader can keep acting after its lease has expired
  • Trace leader election with ZooKeeper's ephemeral nodes or etcd's leases and revisions
  • Protect a shared resource with fencing tokens checked by the resource itself
  • Calculate a lease's renewal interval, guard time and failover gap
  • Choose failure-detection timeouts that trade detection speed against false suspicion

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain how a paused leader can keep acting after its lease has expired
  • Trace leader election with ZooKeeper's ephemeral nodes or etcd's leases and revisions
  • Protect a shared resource with fencing tokens checked by the resource itself
  • Calculate a lease's renewal interval, guard time and failover gap
  • Choose failure-detection timeouts that trade detection speed against false suspicion

Before you start

On this page

Many jobs need exactly one process at a time: the scheduler that starts a nightly report, the owner of a partition, the only writer of a file. If that process holds a 10-second lease and stalls for 15 seconds, a second one takes over meanwhile, and both may write. leader election: Choosing one process out of several to do a job alone, usually through a coordination service, with a lease so that a crashed leader is replaced without anyone's help. picks the one process, usually with the help of a coordination service: A small service replicated with consensus, such as ZooKeeper or etcd, that other systems use for leader election, locks, leases and shared configuration. such as ZooKeeper or etcd. Leases make leadership expire when the leader stops renewing it, so a crashed leader is replaced without anyone’s help. Fencing tokens are numbers that grow with every new leader, and the resource refuses any write that carries an older number than one it has seen. A design needs all three, because leases alone fail in a way that is easy to miss: a leader that is only paused looks exactly like a dead one.

A paused leader outlives its lease, so leases alone are not enough

Leader election on a coordination service takes a few lines. In ZooKeeper each candidate creates an ephemeral node: A ZooKeeper node that exists only as long as the session of the client that created it; when the session ends, ZooKeeper deletes it., sequential node, the lowest sequence number leads, and an ephemeral node vanishes when its owner’s session ends (ZooKeeper recipes; ZooKeeper guide). In etcd each candidate attaches its key to a lease: A time-limited right to act, as a leader or as the holder of a lock. The holder may act only while the lease lasts, and no successor starts before it has expired. More in “Failover, split brain and fencing” that it keeps alive, and the key is deleted when the lease runs out (etcd API). Leases are what make both work, and leases depend on time.

A garbage-collection pause, a stalled virtual machine or a slow network can stop a leader for longer than its lease. The service correctly gives the lease to someone else; the old leader resumes, unaware that any time has passed, and finishes what it was doing. Kleppmann notes that HBase once had exactly this bug (Kleppmann), and Amazon’s Builders’ Library warns that during failures a system may have zero leaders or two (Builders’ Library). Here is the case of the opening, top to bottom in the order things happen:

Worker A pauses past the end of its lease; worker B gets the lease and writes; A wakes and writes too, overwriting B.Worker ALease serviceWorker BStoraget=0: acquireyours until t=10t=4: pauses for 15 st=10: acquireyours until t=20t=11: write v2t=19: write, built on data read at t=4

A paused leader outlives its lease

Text description of the diagram

The diagram shows four participants from left to right: worker A, the lease service, worker B and the storage. Messages are drawn from top to bottom in the order they happen.

  1. At t=0, worker A asks the lease service for the lease and gets it until t=10.
  2. At t=4, worker A pauses for 15 seconds, for example in a long garbage-collection pause.
  3. At t=10, worker B asks for the lease and, since A's has expired, gets it until t=20.
  4. At t=11, worker B writes version 2 to the storage.
  5. At t=19, worker A wakes up and writes to the storage too, with an update built on data it read at t=4. Nothing stops this write, so it overwrites B's.

The program replays the diagram with its times:

A paused leader overwrites its successor Python · two-leaders/two_leaders.py
# Worker A holds a 10-second lease on "report.csv" and pauses (a long garbage-collection pause, say) halfway through
# its update. The lease expires, worker B takes over and writes, then A wakes up and finishes the write it had
# started, believing it still leads. Times are seconds; the storage accepts every write it receives.
LEASE_S = 10

lease = {"holder": None, "expires": 0}
storage = {"report.csv": "v1 (before)"}
log = []


def acquire(worker, t):
    if lease["holder"] is None or t >= lease["expires"]:
        lease.update(holder=worker, expires=t + LEASE_S)
        log.append(f"t={t:>2}  {worker} gets the lease until t={t + LEASE_S}")
        return True
    return False


def write(worker, t, value):
    storage["report.csv"] = value
    log.append(f"t={t:>2}  {worker} writes {value!r}")


acquire("A", 0)
log.append("t= 4  A reads the file and starts its update; then it pauses for 15 s")
for t in range(1, 20):
    if acquire("B", t):  # B keeps trying; it wins as soon as A's lease has expired
        write("B", t + 1, "v2 (from B)")
        break
log.append("t=19  A resumes; as far as it knows, no time has passed")
write("A", 19, "v2 (from A, built from the v1 it read at t=4)")

for line in log:
    print(line)
print(f"\nThe file now holds {storage['report.csv']!r}: B's update is lost, and two workers acted as leader.")

Output

t= 0  A gets the lease until t=10
t= 4  A reads the file and starts its update; then it pauses for 15 s
t=10  B gets the lease until t=20
t=11  B writes 'v2 (from B)'
t=19  A resumes; as far as it knows, no time has passed
t=19  A writes 'v2 (from A, built from the v1 it read at t=4)'

The file now holds 'v2 (from A, built from the v1 it read at t=4)': B's update is lost, and two workers acted as leader.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 two_leaders.py

B’s update is lost, and nothing failed loudly. Checking the lease again just before writing does not help, because the pause can fall between the check and the write. The fixes are structural. fencing token: A number that rises every time a lease or leadership changes hands. The storage refuses a write that carries a lower token than one it has already accepted. More in “Failover, split brain and fencing” let the storage refuse the stale write itself. Lease timing with a monotonic clock, counted from when each renewal was sent, keeps the leader’s own estimate safe. Idempotent work lets a second leader repeat a step without harm. A failure detector, the timeout that declares a leader gone, decides how fast all of this starts. Leader election itself stays simple; the care goes into listing every resource a stale leader could still reach and making each of them refuse it. Fencing tokens do that for anything you can change, which makes them the default for every resource whose corruption you cannot afford.

Key idea

A paused leader cannot tell that its lease ran out, so the resource it writes to has to be the one that refuses it.

Checkpoint

Check: Why leases alone are not enough

  1. Question 1 of 2 A leader checks its lease, sees 3 seconds left, and then writes. Why can that write still come from a stale leader?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A pause can fall between the check and the write and last longer than the 3 seconds

    Checking again only moves the gap; it does not close it. Only something outside the leader, the resource it writes to, can refuse a write that comes too late.

  2. Question 2 of 2 Which of these stops a paused leader's late write from overwriting its successor's?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The storage refusing writes whose fencing token is lower than one it has accepted

    The fixes are structural: a resource that checks tokens, conservative lease timing and work that is safe to repeat. Of these, only the token check stops the write itself.

ZooKeeper and etcd elect the oldest candidate and drop a leader whose session ends Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Fencing tokens let the resource refuse an older leader
  • Count a lease from the request, on a monotonic clock, and keep a guard
  • A failure detector trades detection speed for false alarms
  • Practise: a storage that refuses stale leaders
    • A storage stub that refuses stale leaders
  • Interview questions
    • What is a fencing token, and who checks it?
    • A billing job must run exactly once a minute across a fleet of 20 servers. How do you choose the server that runs it, and what happens when that server stalls for 30 seconds in the middle of a run?
    • How do you choose a lease's duration and its renewal interval? Work through the numbers for a 10-second lease, including clock drift, slow renewals and pauses.
    • Your leader sends updates to a third-party API that cannot check fencing tokens. How do you still stop a stale leader from doing damage?
  • Key takeaways

4 diagrams · 3 runnable examples (Python) · 1 exercise with sample tests · 3 interview answers · about 39 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Design a unique ID generator

Terms in this lesson

coordination service
A small service replicated with consensus, such as ZooKeeper or etcd, that other systems use for leader election, locks, leases and shared configuration.
ephemeral node
A ZooKeeper node that exists only as long as the session of the client that created it; when the session ends, ZooKeeper deletes it.
leader election
Choosing one process out of several to do a job alone, usually through a coordination service, with a lease so that a crashed leader is replaced without anyone's help.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.