16. Jitter Buffer Reordering with a Playback Deadline
cartesia-coding.) Confirmed: Cartesia's realtime WebSocket API streams synthesized audio back to the client in chunks via receive(), and the client is expected to start consuming those chunks as they arrive rather than waiting for the full utterance to finish synthesizing (Source: docs.cartesia.ai/get-started/realtime-text-to-speech-quickstart). Reassembling chunks that may not arrive in generation order, under a bounded buffering budget, is standard practice for real-time audio delivered over a network like this — the specific reorder/eviction policy here is a plausible implementation of that need, not a disclosed Cartesia internal.Problem
Audio packets sent over a real-time connection don't always arrive in the order they were generated — network jitter can reorder or delay them. A jitter buffer holds a small window of recently-arrived packets so it can release them for playback in the correct sequence, but it can't wait forever for a straggler.
Once too many later packets have piled up waiting on one missing earlier packet, the buffer gives up on it, marks that slot lost, and moves on so the rest of the stream doesn't stall. Given the arrival order of sequence numbers (with gaps for packets that never show up at all), a total count, and a buffering capacity, reconstruct the full playback timeline.
Source: src/16_jitter_buffer_reorder.py
def reorder_jitter_buffer(arrivals: list[int], total: int, capacity: int) -> list[int | None]:
...
Examples:
>>> reorder_jitter_buffer([0, 2, 1, 3], 4, 2)
[0, 1, 2, 3]
>>> reorder_jitter_buffer([1, 2, 4, 5], 6, 2)
[None, 1, 2, None, 4, 5]
Step-by-Step Approach
- Maintain a min-heap of arrived-but-not-yet-released sequence numbers, and an
expectedcounter starting at 0 — the next seq the playback timeline needs. - On each arrival, push it onto the heap, then drain: while the heap's smallest value equals
expected, pop it, append it to the output, and incrementexpected. This handles runs of consecutive packets releasing at once. - After draining, check whether the heap has grown past
capacity. If so, the buffer has waited long enough: appendNonefor the stalledexpectedseq, incrementexpected, and re-run the drain step in case that unblocked something already buffered. - Repeat the overflow check in a loop (not just once) — dropping one seq can still leave the heap over capacity, or can immediately unblock several buffered releases.
- Once every arrival has been processed, flush what's left: while
expected < total, release it from the heap if it's the current minimum, otherwise mark it lost, and advance. - Handle the edges: an empty
arrivalslist flushes straight to all-None, and acapacityof 0 still works correctly for packets that happen to arrive in order, because the drain step runs before the overflow check.
The key insight is separating two independent loops — a heap drain that fires whenever the next expected packet becomes available, and a capacity-triggered eviction that only fires when buffering has gone on too long — rather than trying to track wall-clock deadlines directly.
Reference solution
import heapq
def reorder_jitter_buffer(arrivals: list[int], total: int, capacity: int) -> list[int | None]:
heap: list[int] = [] # min-heap of arrived-but-not-yet-released seq numbers
output: list[int | None] = []
expected = 0
for seq in arrivals:
heapq.heappush(heap, seq)
# release every buffered packet that's next in line
while heap and heap[0] == expected:
output.append(heapq.heappop(heap))
expected += 1
# buffer overflowed: give up on the stalled expected seq(s) and keep draining
while len(heap) > capacity:
output.append(None)
expected += 1
while heap and heap[0] == expected:
output.append(heapq.heappop(heap))
expected += 1
# end of stream: resolve any seq numbers that were still pending or never arrived
while expected < total:
if heap and heap[0] == expected:
output.append(heapq.heappop(heap))
else:
output.append(None)
expected += 1
return output
Key Functions & Tricks
heapq.heappush/heapq.heappop— O(log capacity) insert and extract-min for tracking the smallest buffered seq.- Drain-then-evict ordering — always try to release in-order packets before considering a forced drop, so exact-order arrivals never pay an eviction cost even at capacity 0.
while heap and heap[0] == expected— releases every consecutive ready packet in one pass, not just one per arrival.while len(heap) > capacity— a loop, not an if, because a single drop can still leave the buffer over budget or can cascade into further releases.- Final flush loop — resolves every remaining expected slot up to
total, covering packets that never arrived at all as well as ones still stuck in the heap.
How to Recognize This Pattern
The signal is "reconstruct an ordered stream from out-of-order arrivals under a bounded buffer, with explicit loss for stragglers" — a step up from plain k-sorted-stream reordering because the buffer must actively give up and move on rather than waiting indefinitely. A bounded min-heap is the right structure for the ordering half; the eviction half is really a small state machine layered on top. Common variations swap the capacity-based eviction for a wall-clock deadline (compare an event's timestamp against last_release_time + max_wait instead of counting buffered items), which is more realistic but harder to test deterministically. A common pitfall is checking for overflow only once per arrival instead of looping, which misses cascading releases or leaves the buffer still over capacity after a single drop.