What is Leader Election?
In a distributed system, leader election is the process of designating a single node as the coordinator responsible for managing tasks across multiple machines.
Key question: When you have 5 database replicas, which one is the master? If the master dies, how do you pick a new one?
Why Need a Leader?
Scenario: 3 database replicas Problem: All 3 accept writes → Data diverges! Solution: Elect 1 leader - Leader accepts all writes - Followers replicate from leader - Consistent view of data
Use cases:
- Database replication (Postgres master)
- Distributed locks (who runs the cron job?)
- Task coordination (MapReduce job tracker)
- Configuration management (etcd leader)
The Split-Brain Problem
Initial state: ┌──────┐ ┌──────┐ ┌──────┐ │Node A│ │Node B│ │Node C│ │LEADER│ │follower│ │follower│ └──────┘ └──────┘ └──────┘ Network partition: ┌──────┐ │ ┌──────┐ ┌──────┐ │Node A│ │ │Node B│ │Node C│ │LEADER│ │ │follower│ │follower│ └──────┘ │ └──────┘ └──────┘ Partition 1 │ Partition 2 Both sides elect leaders: ┌──────┐ │ ┌──────┐ ┌──────┐ │Node A│ │ │Node B│ │Node C│ │LEADER│ │ │LEADER│ │follower│ └──────┘ │ └──────┘ └──────┘ PROBLEM: Two leaders accepting writes!
Consensus approach: Require a majority (floor(N/2) + 1) under protocol rules that prevent conflicting votes. A quorum count alone does not fence a stale leader from external resources.
3 nodes: Need 2 votes (quorum) 5 nodes: Need 3 votes (quorum) Partition 1: Node A (1 vote) → Can't become leader Partition 2: Nodes B, C (2 votes) → Can elect leader Only one partition can have quorum!
Consensus Algorithms
1. Bully Algorithm
Simple ID-based election: Highest ID wins.
Process:
- Node suspects the leader is unreachable
- Sends election message to all higher-ID nodes
- If no response from higher-ID nodes, declares itself leader
- If higher-ID node responds, that node starts election
Implementation:
class BullyElection:
def __init__(self, node_id, all_nodes):
self.node_id = node_id
self.all_nodes = sorted(all_nodes)
self.leader = None
self.in_election = False
def start_election(self):
"""Start election process"""
print(f"Node {self.node_id}: Starting election")
self.in_election = True
# Find nodes with higher IDs
higher_nodes = [n for n in self.all_nodes if n > self.node_id]
if not higher_nodes:
# I have highest ID - become leader
self.become_leader()
return
# Send election messages to higher nodes
responses = []
for node in higher_nodes:
response = self.send_election_message(node)
if response:
responses.append(response)
if responses:
# Higher node responded - wait for them to win
print(f"Node {self.node_id}: Higher node responded, waiting...")
else:
# No response from higher nodes - I win!
self.become_leader()
def become_leader(self):
"""Declare self as leader"""
self.leader = self.node_id
self.in_election = False
print(f"Node {self.node_id}: I AM THE LEADER!")
# Announce to all lower nodes
for node in self.all_nodes:
if node < self.node_id:
self.send_coordinator_message(node)
def send_election_message(self, target_node):
"""Send election message to target node"""
# Simulate message - returns True if node alive
return is_node_alive(target_node)
def send_coordinator_message(self, target_node):
"""Announce leadership to node"""
# Notify node of new leader
pass
def receive_election_message(self, from_node):
"""Handle incoming election message"""
if from_node < self.node_id:
# I have higher ID - respond and start my own election
print(f"Node {self.node_id}: Received election from {from_node}, bullying...")
self.start_election()
return True
return False
# Example usage
nodes = [1, 2, 3, 4, 5]
node3 = BullyElection(node_id=3, all_nodes=nodes)
# Node 5 (leader) dies
# Node 3 notices and starts election
node3.start_election()
# Output: Node 3 becomes leader (highest alive ID)
Pros:
- Simple to implement
- Elects the highest reachable ID under its timing and communication assumptions
Cons:
- Many messages (O(n²) in worst case)
- Not fault-tolerant during election
- Can have multiple elections simultaneously
2. Raft Consensus
Modern, understandable consensus algorithm. Used by etcd (Kubernetes), Consul, and CockroachDB.
Key concepts:
- Leader: Handles all client requests
- Followers: Replicate leader's log
- Candidates: Nodes trying to become leader
States:
┌──────────┐ timeout ┌───────────┐ wins ┌────────┐
│ Follower │───────────▶│ Candidate │───────────▶│ Leader │
└──────────┘ └───────────┘ └────────┘
▲ │ │
│ │ loses │
│ ▼ │
└────────────────────────────────────────────────┘
Election process:
What a safe election implementation must preserve
A vote counter alone is not Raft. A candidate carries its term and last-log metadata. A voter checks log freshness, persists its term/vote before responding, and votes at most once per term. The candidate counts distinct votes only for its current election; delayed responses from an older term cannot make it leader.
A majority is floor(voting_members / 2) + 1, including the candidate's own vote. Membership changes need the protocol's configuration-change rules. Use a maintained consensus implementation rather than a short timer/RPC example for authoritative state.
Raft guarantees:
- Safety: At most one leader per term
- Liveness: Progress requires a communicating majority and suitable timing conditions
- Log replication: Only committed entries are authoritative; uncommitted entries may be overwritten
Pros:
- Understandable (simpler than Paxos)
- Production-ready implementations
- Strong consistency guarantees
Cons:
- Complex to implement correctly
- Network partition sensitive
- Leader bottleneck for writes
3. Paxos
Classic consensus algorithm (Leslie Lamport, 1989). Very difficult to understand and implement.
Roles:
- Proposers: Propose values
- Acceptors: Vote on proposals
- Learners: Learn chosen value
Phases:
Phase 1: Prepare - Proposer sends prepare(n) to acceptors - Acceptors promise not to accept proposals < n Phase 2: Accept - Proposer sends accept(n, value) - Acceptors accept if >= highest promise
Why it's hard: Subtle edge cases, liveness not guaranteed, difficult to implement.
Modern variants:
- Multi-Paxos: Optimized for multiple decisions
- Fast Paxos: Faster under low contention
- Raft: Easier-to-understand alternative
Quorum Systems
Definition: Subset of nodes that can make progress.
3 nodes: Quorum = 2 5 nodes: Quorum = 3 7 nodes: Quorum = 4 Formula: Quorum = ⌊N/2⌋ + 1
Why quorum works:
5 nodes split into 2 partitions: Partition A: 3 nodes (has quorum) ✓ Partition B: 2 nodes (no quorum) ✗ Only Partition A can elect leader!
Trade-offs:
More nodes: + Higher availability (tolerate more failures) - Slower consensus (need more votes) - More expensive Fewer nodes: + Faster consensus + Cheaper - Lower availability
Real-World Systems
Kubernetes (etcd)
etcd uses Raft for leader election 3 or 5 voting members is common; odd counts use majority fault tolerance efficiently Leader handles all writes Followers replicate If leader dies: - Followers reach their configured election timeout - New election starts - Recovery time depends on timing and available quorum
Apache ZooKeeper
ZooKeeper uses ZAB (ZooKeeper Atomic Broadcast) Similar to Paxos/Raft Used by: - Older Kafka deployments used ZooKeeper; current Kafka uses KRaft - HBase (master election) - Hadoop (NameNode HA)
Consul
Uses Raft for consensus Deploy 3 or 5 server nodes Leader handles writes Followers forward to leader Used for service discovery + KV store
Implementation Patterns
Using etcd for Leader Election
// Go client for etcd leader election
package main
import (
"context"
"fmt"
"go.etcd.io/etcd/client/v3/concurrency"
clientv3 "go.etcd.io/etcd/client/v3"
)
func main() {
client, _ := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
})
defer client.Close()
session, _ := concurrency.NewSession(client)
defer session.Close()
election := concurrency.NewElection(session, "/my-service/leader")
// Campaign to become leader (blocks until elected)
ctx := context.Background()
if err := election.Campaign(ctx, "node-1"); err != nil {
fmt.Println("Failed to campaign:", err)
return
}
fmt.Println("I AM THE LEADER!")
// Do leader work...
doLeaderWork()
// Resign leadership
election.Resign(ctx)
}
func doLeaderWork() {
// Only one node executes this at a time
fmt.Println("Doing critical work as leader...")
}
A lease is not a fencing mechanism
A SET key owner NX PX ttl lease can expire while its old holder is paused. A new holder may then acquire it; when the old process resumes, both can attempt work. Comparing the owner token atomically before renewal/deletion protects the lease key, but does not prevent a stale process from writing to a different database.
For safety-critical work, require monotonically increasing fencing tokens that the protected resource validates, or perform the action within an appropriate transactional/consensus boundary. Decide what happens when the lease service is unreachable. A local boolean named isLeader is not sufficient authority.
Interview Tips 💡
When discussing leader election in system design interviews:
- Explain the need: "Multiple database replicas need one master for consistent writes..."
- Quorum math: "With 5 nodes, need 3 votes to prevent split-brain..."
- Choose algorithm: "Use Raft (via etcd) for simplicity and proven implementation..."
- Handle failures: "If leader dies, followers timeout after 150-300ms and start election..."
- Discuss trade-offs: "3 nodes cheaper but 5 nodes more fault-tolerant..."
- Real examples: "Kubernetes uses etcd with Raft, modern Kafka uses KRaft..."
Related Concepts
- Distributed Consensus — Agreement across nodes
- CAP Theorem — Consistency vs availability trade-offs
- Database Replication — Master-slave patterns
- Quorum — Majority voting systems
- Split-Brain Problem — Partition handling
Sources and a failure experiment
Raft's published paper defines election safety and log restrictions. etcd concurrency client documentation documents sessions and election primitives. Pause the old leader past its lease, elect a successor, then resume it: the protected resource must reject stale writes even if the old process still believes it leads.
Keep reading
Raft Consensus Algorithm
A comprehensive guide to Raft, the consensus algorithm powering Etcd, Consul, and Kubernetes. Leader election, log replication, safety guarantees, and production deployment patterns.
Merkle Trees (Data Synchronization)
Hash tree data structure enabling efficient verification of data integrity and synchronization across distributed systems, used in Git, Bitcoin, Cassandra, and IPFS for tamper detection and incremental sync.
Cache Eviction Policies
When the cache is full, something has to go. A comprehensive guide to LRU, LFU, ARC, and other replacement algorithms with implementation details.
CAP Theorem
Reason about a network partition using a reservation example, quorum limits and operation-specific consistency choices.
File Synchronization: Versions, Conflicts and Recovery
A worked file-sync design: immutable blocks, version checks, offline conflicts, and safe cleanup. Includes a clearly scoped Dropbox engineering reference.
Geohashing (Location Encoding)
A geocoding system that encodes latitude/longitude coordinates into short alphanumeric strings for efficient proximity searches and spatial indexing.