Implement a fixed-capacity FIFO queue. Without a bound, a fast producer can fill heap before consumers keep up — that's how OOMs happen in pipelines. Bounded queues are the simplest backpressure primitive.
This non-blocking variant is the building block for the producer/consumer stages in a streaming masker — a real implementation adds await/signal, but the storage shape is the same.
Tests this task must pass
FIFO order — three puts (1, 2, 3) then three takes return 1, 2, 3 in that order.
Capacity enforced — third put on a 2-slot queue returns false rather than overflowing.
Empty behaviour — fresh queue is isEmpty; take() on empty returns null (not throw).
Size tracking — size reflects each put/take immediately.
Fill and drain — full queue reports isFull; after draining all elements in order, queue is isEmpty.
//sampleStart
class BoundedQueue<T>(private val capacity: Int) {
// Implement a fixed-capacity FIFO queue
// put(item): add to tail. If full → return false
// take(): remove from head. If empty → return null
// size, isEmpty, isFull properties
fun put(item: T): Boolean { TODO() }
fun take(): T? { TODO() }
val size: Int get() = TODO()
val isEmpty: Boolean get() = TODO()
val isFull: Boolean get() = TODO()
}
//sampleEnd
fun main() {
// Test 1: Basic put/take
val q = BoundedQueue<Int>(5)
q.put(1); q.put(2); q.put(3)
check(q.take() == 1) { "FAIL: FIFO — first in first out" }
check(q.take() == 2) { "FAIL: FIFO" }
check(q.take() == 3) { "FAIL: FIFO" }
println("✅ Test 1: FIFO order")
// Test 2: Capacity
val q2 = BoundedQueue<String>(2)
check(q2.put("a")) { "FAIL: should accept" }
check(q2.put("b")) { "FAIL: should accept" }
check(!q2.put("c")) { "FAIL: queue full, should reject" }
println("✅ Test 2: Capacity enforced")
// Test 3: Empty
val q3 = BoundedQueue<Int>(3)
check(q3.isEmpty) { "FAIL: new queue is empty" }
check(q3.take() == null) { "FAIL: take from empty returns null" }
println("✅ Test 3: Empty queue")
// Test 4: Size tracking
val q4 = BoundedQueue<Int>(10)
q4.put(1); q4.put(2); q4.put(3)
check(q4.size == 3) { "FAIL: size should be 3" }
q4.take()
check(q4.size == 2) { "FAIL: size should be 2 after take" }
println("✅ Test 4: Size tracking")
// Test 5: Fill and drain
val q5 = BoundedQueue<Int>(3)
q5.put(10); q5.put(20); q5.put(30)
check(q5.isFull) { "FAIL: should be full" }
check(q5.take() == 10 && q5.take() == 20 && q5.take() == 30) { "FAIL: drain order" }
check(q5.isEmpty) { "FAIL: should be empty after drain" }
println("✅ Test 5: Fill and drain")
println("\n🎉 ALL TESTS PASSED!")
}
Hint
ArrayDeque<T> gives you O(1) addLast/removeFirst. The class needs nothing more than a deque plus a capacity check on put.
Solution
class BoundedQueue<T>(private val capacity: Int) {
private val items = ArrayDeque<T>(capacity)
fun put(item: T): Boolean {
if (items.size >= capacity) return false
items.addLast(item)
return true
}
fun take(): T? = if (items.isEmpty()) null else items.removeFirst()
val size: Int get() = items.size
val isEmpty: Boolean get() = items.isEmpty()
val isFull: Boolean get() = items.size >= capacity
}