Implement a consistent hashing ring
Company: DoorDash
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Onsite
Quick Answer: This question evaluates understanding of consistent hashing, virtual nodes, deterministic hash functions, and data structure design for efficient key-to-server mapping and dynamic membership changes.
Constraints
- 1 <= replicas_per_server <= 1000
- 1 <= len(operations) == len(arguments) <= 2 * 10^5
- The number of active virtual nodes never exceeds 2 * 10^5
- 1 <= len(server_id) <= 100 and 0 <= len(key) <= 100
- Server IDs are unique while active; removing a missing server should do nothing
Examples
Input: (3, ['get_server'], [['qS']])
Expected Output: [None]
Explanation: The ring is empty, so 'get_server' must return None.
Input: (3, ['add_server', 'add_server', 'get_server', 'get_server', 'get_server', 'get_server'], [['A'], ['B'], ['qS'], ['qT'], ['qU'], ['zz']])
Expected Output: [None, None, 'A', 'A', 'B', 'A']
Explanation: With 3 replicas: A -> 279, 282, 285 and B -> 280, 283, 286. 'qS' hashes to 279 -> A, 'qT' hashes to 281 so it advances to 282 -> A, 'qU' hashes to 283 -> B, and 'zz' hashes to 366 so it wraps to the smallest position 279 -> A.
Hints
- Think of each virtual node as a point on a sorted circle. Then 'get_server' becomes a successor/lower_bound query with wrap-around.
- Store every replica token created for a server so you can delete them quickly later. Using keys like (position, replica_token) also makes hash collisions deterministic.