asynchronous programming
sequences merging
concurrency
programming tutorials
bias avoidance

How to merge multiple asynchronous sequences without left-side bias?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

When you merge asynchronous sequences, a naive implementation often favors whichever source is checked first. That left-side bias can increase latency for later sources and make the merged output unfair under load. A better design waits for whichever source becomes ready first and rotates fairly when several sources are simultaneously available.

Why Left-Side Bias Happens

Bias usually appears when code polls sources in a fixed order. If source A is always checked before source B, then A can dominate the merged stream whenever both have data available.

A toy Python example shows the problem conceptually.

python
1from collections import deque
2
3left = deque(["A1", "A2", "A3"])
4right = deque(["B1", "B2", "B3"])
5merged = []
6
7while left or right:
8    if left:
9        merged.append(left.popleft())
10    elif right:
11        merged.append(right.popleft())
12
13print(merged)

This is not truly asynchronous, but it shows the bias clearly: the left side wins until it is empty.

In real async code, the equivalent mistake is awaiting sources in a fixed sequence instead of responding to whichever one becomes ready.

Merge By Waiting For Ready Producers

A fairer pattern is to have each producer publish items into a shared queue as soon as they are ready. The consumer then reads from that queue in arrival order instead of polling producers left to right.

python
1import asyncio
2
3
4async def producer(name, delay, values, output):
5    for value in values:
6        await asyncio.sleep(delay)
7        await output.put((name, value))
8    await output.put((name, None))
9
10
11async def fair_merge():
12    queue = asyncio.Queue()
13
14    producers = [
15        asyncio.create_task(producer("left", 0.15, [1, 2, 3], queue)),
16        asyncio.create_task(producer("right", 0.10, [10, 20, 30], queue)),
17    ]
18
19    finished = 0
20    results = []
21
22    while finished < len(producers):
23        source, value = await queue.get()
24        if value is None:
25            finished += 1
26            continue
27        results.append((source, value))
28
29    await asyncio.gather(*producers)
30    return results
31
32
33print(asyncio.run(fair_merge()))

This design is not biased toward the leftmost producer. Items are processed when they become available.

Adding Fairness When Several Sources Are Ready

Arrival order removes fixed left-side polling bias, but if multiple sources can enqueue aggressively, you may still want explicit fairness. A common strategy is round-robin draining: once you receive one item from a source, give other ready sources a chance before taking another from the same source.

The simplest architecture is to keep one queue per source and rotate across queues.

python
1import asyncio
2
3
4async def produce(values, delay, queue):
5    for value in values:
6        await asyncio.sleep(delay)
7        await queue.put(value)
8    await queue.put(None)
9
10
11async def round_robin_merge():
12    queues = [asyncio.Queue(), asyncio.Queue()]
13    tasks = [
14        asyncio.create_task(produce([1, 2, 3], 0.05, queues[0])),
15        asyncio.create_task(produce([10, 20, 30], 0.05, queues[1])),
16    ]
17
18    active = [True, True]
19    results = []
20    index = 0
21
22    while any(active):
23        queue = queues[index]
24        if active[index] and not queue.empty():
25            value = await queue.get()
26            if value is None:
27                active[index] = False
28            else:
29                results.append(value)
30        index = (index + 1) % len(queues)
31        await asyncio.sleep(0)
32
33    await asyncio.gather(*tasks)
34    return results
35
36
37print(asyncio.run(round_robin_merge()))

This is a small example, but the principle scales: separate readiness from fairness policy.

Design Guidance

If your sources represent genuine event streams, preserving actual arrival order is often the right behavior. If your problem is more like work scheduling, a round-robin or weighted policy may be better.

The important point is to avoid serially awaiting source A, then source B, then source C in a loop. That structure bakes bias into the merge algorithm.

Common Pitfalls

A common mistake is awaiting each async iterator in declaration order. That makes later sources wait even when they already have items ready.

Another mistake is trying to solve fairness only after values have already been merged through a biased poller. At that point, the damage is already done.

Developers also sometimes confuse throughput with fairness. A highly active source may still produce more items overall without the algorithm being biased. Fairness means every ready source gets a chance, not that all sources emit identical counts.

Finally, if you use queues, make sure completion signaling is explicit. Without an end-of-stream marker or task tracking, the consumer may wait forever.

Summary

  • Left-side bias comes from checking or awaiting sources in a fixed order.
  • A shared queue removes fixed polling bias by consuming items as they arrive.
  • Per-source queues plus round-robin logic add stronger fairness when several sources are ready.
  • Keep arrival policy and fairness policy separate in the design.
  • Explicit completion signaling is necessary in queue-based merges.
  • Test the merge under uneven producer speeds to confirm that no source is starved.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.