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.
Input: (2, ['add_server', 'add_server', 'add_server', 'get_server', 'remove_server', 'get_server', 'remove_server', 'get_server', 'remove_server', 'get_server'], [['A'], ['B'], ['C'], ['qT'], ['C'], ['qT'], ['A'], ['qT'], ['B'], ['qT']])
Expected Output: [None, None, None, 'C', None, 'A', None, 'B', None, None]
Explanation: With 2 replicas: A -> 279, 282; B -> 280, 283; C -> 281, 284. 'qT' hashes to 281, so it first maps to C. After removing C it maps to A at 282, after removing A it maps to B at 283, and after removing B the ring is empty.
Input: (1, ['add_server', 'get_server', 'remove_server', 'get_server'], [['A'], [''], ['A'], ['']])
Expected Output: [None, 'A', None, None]
Explanation: The empty string hashes to 0. With only A#0 at 279, it maps to A. After removing A, the ring is empty again.
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.