Design Phone Directory — Available Number Pool Management
Advertisement
Problem Statement
Design a phone directory that manages maxNumbers phone numbers. Implement three operations: get() provides an available number and returns -1 if none; check(number) returns whether a number is available; release(number) recycles a number back to the pool.
Constraints:
- 1 <= maxNumbers <= 10^4
- 0 <= number < maxNumbers
- At most 2 * 10^4 calls total to
get,check, andrelease
Input: maxNumbers=3, get(), get(), check(2), get(), check(2), release(2), check(2)
Output: 0, 1, true, 2, false, —, trueInput: maxNumbers=1, get(), get()
Output: 0, -1Why This Problem Matters
Resource pool management is a fundamental pattern in systems programming. Database connection pools, thread pools, IP address allocation (DHCP), and memory allocators all implement the same core abstraction: a finite pool of identifiers that can be acquired and released. Understanding this design is essential for senior engineering interviews at Amazon AWS (where EC2 instance ID allocation uses similar logic) and Google Cloud.
In FAANG interviews, this problem tests whether you can design a bounded resource allocator with O(1) operations. Naive approaches using sorted sets or linear scans fail to achieve the required constant-time guarantees. The queue-plus-boolean-array design is the canonical solution.
This same pattern appears in operating system process ID allocation, port number assignment in network stacks, and UUID pool management in distributed systems.
The Core Insight
Maintain two data structures: a queue of available numbers (initialised with all numbers 0 to maxNumbers-1) and a boolean array used[] to track allocation status. get() dequeues from the free queue and marks the number as used. check(n) simply reads used[n]. release(n) marks the number as free and enqueues it.
All three operations are O(1). The queue ensures fair FIFO allocation—numbers are reused in the order they were released, which is important for audit trails and debugging. The boolean array provides O(1) availability checks without scanning the queue.
A HashSet alternative uses O(N) average for all operations but avoids the fixed-size array limitation. Use the queue-plus-array approach when the number space is bounded and known in advance.
Visual Dry Run
| Call | free queue | used[] | Result |
|---|---|---|---|
| init(3) | [0,1,2] | [F,F,F] | — |
| get() | [1,2] | [T,F,F] | 0 |
| get() | [2] | [T,T,F] | 1 |
| check(2) | — | [T,T,F] | true |
| get() | [] | [T,T,T] | 2 |
| check(2) | — | [T,T,T] | false |
| release(2) | [2] | [T,T,F] | — |
| check(2) | — | [T,T,F] | true |
Solution (Optimal)
from collections import deque
class PhoneDirectory:
def __init__(self, maxNumbers: int):
self.available = deque(range(maxNumbers))
self.in_use = set()
def get(self) -> int:
if not self.available:
return -1
num = self.available.popleft()
self.in_use.add(num)
return num
def check(self, number: int) -> bool:
return number not in self.in_use
def release(self, number: int) -> None:
if number in self.in_use:
self.in_use.remove(number)
self.available.append(number)class PhoneDirectory {
constructor(maxNumbers) {
this.queue = [];
this.used = new Array(maxNumbers).fill(false);
for (let i = 0; i < maxNumbers; i++) this.queue.push(i);
this.head = 0;
}
get() {
if (this.head >= this.queue.length) return -1;
const num = this.queue[this.head++];
this.used[num] = true;
return num;
}
check(n) {
return !this.used[n];
}
release(n) {
if (this.used[n]) {
this.used[n] = false;
this.queue.push(n);
}
}
}Time: O(1) for all three operations
Space: O(N) — queue and boolean array each of size maxNumbers
Common Mistakes
- Not checking
if number in self.in_usebefore releasing—releasing a free number should be a no-op, not add it twice to the queue - Using a list with
pop(0)instead of a deque—O(N) pergetdue to list shifting - Forgetting to guard
releaseagainst double-release—without the guard, the same number appears twice in the queue and can be returned by two consecutiveget()calls - Not initialising all numbers in the queue at startup—leaving some numbers permanently unavailable
- Using
in self.availableto check a deque which is O(N)—use the boolean array or set for O(1) availability checks
Interview Tips
- Explain the two components clearly: queue for ordering/allocation, boolean array for O(1) availability lookup
- Mention the connection pool analogy: this is exactly how database drivers like JDBC manage connection availability
- Discuss the double-release protection: in production systems, releasing a resource you do not own is a serious bug and must be guarded
- For the follow-up about concurrency, mention
synchronizedblocks in Java orasyncio.Lockin Python to protect the queue and array together
Follow-up Questions
- How would you handle concurrent
get()calls from multiple threads? (Lock the queue-and-array pair atomically; useReentrantLockin Java orasyncio.Lockin Python) - What if numbers could have priority levels—allocate low numbers first? (Use a min-heap instead of a queue)
- How would you support returning the current allocation count in O(1)? (Maintain a counter variable incremented on
getand decremented onrelease) - How would you handle automatic reclamation of numbers not released after a timeout? (Store allocation timestamp; background thread reclaims expired numbers)
- How does this scale to a billion phone numbers in a distributed system? (Shard the number space across nodes; each node owns a range and manages its own pool)
Key Takeaways
- A deque (queue) of free numbers plus a boolean availability array gives O(1) get, check, and release
- Always guard
releaseagainst double-release: only re-enqueue if the number was actually in use - Use
deque.popleft()notlist.pop(0)—the latter is O(N) due to list element shifting - This pattern is used verbatim for connection pool management, DHCP IP allocation, and OS process ID allocation
- The queue ensures FIFO reuse ordering; a min-heap can replace it if lowest-numbered-first allocation is needed
check(n)should use the boolean array or set for O(1) lookup, not scan the queue- The space complexity is O(maxNumbers) regardless of how many numbers are currently allocated
Advertisement