Design a queue and analyze tradeoffs
Company: Optiver
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of FIFO data structures, algorithmic complexity analysis, memory and cache behavior, and concurrent access concerns.
Constraints
- 0 <= capacity <= 10^5
- 0 <= len(operations) <= 2 * 10^5
- Queue values are integers in the range [-10^9, 10^9]
- Each operation is one of 'enqueue', 'dequeue', 'peek', or 'isEmpty'
- Assume single-threaded execution; concurrency discussion is a follow-up, not part of the implementation
Examples
Input: ([('enqueue', 1), ('enqueue', 2), ('peek',), ('dequeue',), ('enqueue', 3), ('dequeue',), ('dequeue',), ('isEmpty',)], 2)
Expected Output: [True, True, 1, 1, True, 2, 3, True]
Explanation: After removing 1, the enqueue of 3 wraps around into the freed slot. The remaining dequeues return 2 and then 3.
Input: ([('enqueue', 5), ('enqueue', 6), ('enqueue', 7), ('isEmpty',), ('peek',), ('dequeue',), ('dequeue',), ('dequeue',)], 2)
Expected Output: [True, True, False, False, 5, 5, 6, 'underflow']
Explanation: The third enqueue fails because the queue is full. After removing 5 and 6, one more dequeue underflows.
Input: ([('isEmpty',), ('enqueue', 10), ('peek',), ('dequeue',)], 0)
Expected Output: [True, False, 'underflow', 'underflow']
Explanation: A queue with capacity 0 is always empty, cannot accept new items, and both peek and dequeue underflow.
Input: ([('enqueue', -1), ('enqueue', -1), ('dequeue',), ('peek',), ('isEmpty',)], 3)
Expected Output: [True, True, -1, -1, False]
Explanation: Negative values and duplicates should be handled normally in FIFO order.
Input: ([], 4)
Expected Output: []
Explanation: With no operations, the result list is empty.
Input: ([('enqueue', 9), ('enqueue', 10), ('dequeue',), ('enqueue', 10), ('peek',), ('isEmpty',)], 1)
Expected Output: [True, False, 9, True, 10, False]
Explanation: Capacity 1 checks single-slot behavior: the queue alternates between full and empty correctly.
Solution
def solution(operations, capacity):
buffer = [None] * capacity if capacity > 0 else []
front = 0
size = 0
results = []
for op in operations:
cmd = op[0]
if cmd == 'enqueue':
if size == capacity:
results.append(False)
else:
insert_idx = (front + size) % capacity
buffer[insert_idx] = op[1]
size += 1
results.append(True)
elif cmd == 'dequeue':
if size == 0:
results.append('underflow')
else:
value = buffer[front]
buffer[front] = None
front = (front + 1) % capacity if capacity > 0 else 0
size -= 1
results.append(value)
elif cmd == 'peek':
if size == 0:
results.append('underflow')
else:
results.append(buffer[front])
elif cmd == 'isEmpty':
results.append(size == 0)
else:
raise ValueError('Invalid operation: ' + str(cmd))
return resultsExplanation
Time complexity: O(m), where m = len(operations) — each operation does O(1) work (index math, one array read/write).. Space complexity: O(capacity) for the pre-allocated buffer, plus O(m) for the returned results list..
Hints
- Track the index of the front element and the current size. The next insertion position can be computed from these values.
- A circular buffer lets you reuse freed slots with modulo arithmetic instead of shifting elements after every dequeue.