Implement a Consistent Hashing Ring From Scratch

Read the full interview experience this question came from →

Quick Overview

Implement a consistent hashing ring that maps string keys to server nodes with add, remove and lookup operations. Tests sorted-position lookup with wrap-around, deterministic hashing across processes, virtual nodes for load balance, operation complexity and how few keys move when membership changes.

Implement a Consistent Hashing Ring From Scratch

Company: Apple

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

Implement a consistent hashing ring from scratch. The ring assigns keys, such as cache keys or request IDs, to a changing set of server nodes. When a node joins or leaves, only a small fraction of the keys may change owner, unlike `hash(key) % node_count`, which reassigns almost every key whenever the node count changes. A possible interface (adjust it as you clarify): ```python class ConsistentHashRing: def __init__(self, nodes: list[str] | None = None): ... def add_node(self, node: str) -> None: ... def remove_node(self, node: str) -> None: ... def get_node(self, key: str) -> str | None: ... ``` Implement the ring, then explain the cost of each operation, how evenly it spreads keys across the nodes, and how many keys move when a node is added or removed. ```hint Find the owner quickly Think about how to store the nodes' positions on the ring so that "the first position at or after this hash" is a fast lookup, including when the hash is past the largest position. ``` ```hint Few nodes, uneven arcs With only a handful of nodes at random positions, the arcs between neighboring positions can differ a lot in length. Consider what would make each node's total share of the ring more even. ``` ### Constraints and Clarifications - Keys and node IDs are strings, and node IDs are unique. - Every process that builds the ring from the same node list must map each key to the same node, so the hash must be deterministic across processes and restarts. Python's built-in `hash()` for strings is randomized per process and does not qualify. - Write the ring yourself; standard-library helpers such as `bisect` and `hashlib` are fine. ### Clarifying Questions - How many nodes and how many lookups per second are expected, and how often does membership change? - Do all nodes have the same capacity, or should some receive proportionally more keys? - Does a caller need several distinct nodes per key, for example for replication? - What should `get_node` return when the ring has no nodes? - Will lookups run concurrently with `add_node` and `remove_node`? ### What a Strong Answer Covers - A sorted structure of ring positions with binary-search lookup and wrap-around past the largest position - A deterministic, well-distributed hash and a defined behavior when two positions collide - Several positions per node to balance load, with the cost in memory and update time - Correct add and remove, including removing every position of a node, and the empty-ring case - The complexity of each operation and the fraction of keys that move when one node joins or leaves, compared with modulo hashing ### Follow-up Questions - Return `R` distinct nodes for a key for replication. How do you walk the ring when one node owns many positions? - One node has twice the capacity of the others. How do you give it twice the keys? - After adding a node, how do you determine which key ranges must be copied to it and from which nodes? - Compare the ring with rendezvous (highest random weight) hashing and with jump consistent hashing. When would you pick each?

Overview: Implement a consistent hashing ring that maps string keys to server nodes with add, remove and lookup operations. Tests sorted-position lookup with wrap-around, deterministic hashing across processes, virtual nodes for load balance, operation complexity and how few keys move when membership changes.

Read the full Apple Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/Apple
Apple logo
Apple
Jul 3, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
0
0

Implement a consistent hashing ring from scratch. The ring assigns keys, such as cache keys or request IDs, to a changing set of server nodes. When a node joins or leaves, only a small fraction of the keys may change owner, unlike hash(key) % node_count, which reassigns almost every key whenever the node count changes.

A possible interface (adjust it as you clarify):

class ConsistentHashRing:
    def __init__(self, nodes: list[str] | None = None): ...
    def add_node(self, node: str) -> None: ...
    def remove_node(self, node: str) -> None: ...
    def get_node(self, key: str) -> str | None: ...

Implement the ring, then explain the cost of each operation, how evenly it spreads keys across the nodes, and how many keys move when a node is added or removed.

Constraints and Clarifications

  • Keys and node IDs are strings, and node IDs are unique.
  • Every process that builds the ring from the same node list must map each key to the same node, so the hash must be deterministic across processes and restarts. Python's built-in hash() for strings is randomized per process and does not qualify.
  • Write the ring yourself; standard-library helpers such as bisect and hashlib are fine.

Clarifying Questions Guidance

  • How many nodes and how many lookups per second are expected, and how often does membership change?
  • Do all nodes have the same capacity, or should some receive proportionally more keys?
  • Does a caller need several distinct nodes per key, for example for replication?
  • What should get_node return when the ring has no nodes?
  • Will lookups run concurrently with add_node and remove_node ?

What a Strong Answer Covers Guidance

  • A sorted structure of ring positions with binary-search lookup and wrap-around past the largest position
  • A deterministic, well-distributed hash and a defined behavior when two positions collide
  • Several positions per node to balance load, with the cost in memory and update time
  • Correct add and remove, including removing every position of a node, and the empty-ring case
  • The complexity of each operation and the fraction of keys that move when one node joins or leaves, compared with modulo hashing

Follow-up Questions Guidance

  • Return R distinct nodes for a key for replication. How do you walk the ring when one node owns many positions?
  • One node has twice the capacity of the others. How do you give it twice the keys?
  • After adding a node, how do you determine which key ranges must be copied to it and from which nodes?
  • Compare the ring with rendezvous (highest random weight) hashing and with jump consistent hashing. When would you pick each?