skip to content

Three dashboard panels subscribe to one source built around a query, and that query runs three times - why?

level: middleimportance: must knowfreq 70%

answer

  1. count the runs, not the panels
  2. subscribing is what starts the work
  3. each panel gets a private execution
  4. cold means per-subscriber re-execution
  5. one shared run needs a multicasting stage

basics

~10 s

The source is cold: it describes work instead of sharing a sequence that is already running, so every subscriber starts a fresh, independent run. Three panels subscribed, so the query executed three times.

solid answer

~40 s

The panels share a source *description*, not a running sequence. A **cold** source starts its work again for each subscriber, so subscriber count and execution count move together: three panels, three query executions, three independent sequences with their own start times, their own values and their own ending. Nothing is wrong with the panels and nothing is missing from a cache. If one execution is what you want, you insert a **multicasting stage** between the query and the panels: it subscribes upstream once and fans every value out to all attached panels, which makes the source live from the panels' side. That is a real design change, not a tuning flag - it gives the panels shared fate, one set of query parameters, and a start/stop policy somebody now has to own.

code

pseudocode · 12 lines
pseudocode
runs = 0

source = cold_source(function on_subscribe(sink):
    runs = runs + 1              // the query is issued here, once per subscriber
    for each row in run_query():
        sink.emit(row)
    sink.complete()
)

source.subscribe(panel_a)        // runs is now 1
source.subscribe(panel_b)        // runs is now 2
source.subscribe(panel_c)        // runs is now 3

go deeper

for a junior

Remember the rule of thumb: with a cold source, work happens once per subscriber. Two panels on one such source means the underlying call happens twice.

for a middle

Explain the mechanism, not just the label: the source holds a description, subscribing starts a run, and each run has its own values, timing and ending. Say how you would count executions to prove it.

for a senior

Show the operational angle. Multiply upstream cost by the number of panels a real session opens, then say what a shared run buys and what it costs in shared failure, fixed parameters and a start/stop policy.

for a principal

Frame it as a published contract. Whether a data source hands each consumer its own run or one shared sequence changes what consuming teams may assume about freshness, parameters and blast radius, so it belongs in the contract rather than in an implementation detail.

## The symptom and the mechanism A **cold source** is a description of work, not a sequence that is already flowing. Subscribing is the act that starts that work, and it starts it **per subscriber**: each subscriber drives its own execution, receives its own values, and gets its own ending. Three panels subscribed to one cold source, so the query behind it ran three times. This is the most common surprise in reactive code precisely because the code *looks* like sharing. One variable holds one source, several panels use that one variable, so it reads as one thing being observed by three observers. What the variable actually holds is a recipe, and each panel cooks it separately. ## Why the three runs are genuinely independent - Each run has its **own start time**, so the three panels can legitimately render slightly different numbers even though they name one source. - Each run has its **own failure**: an error in one panel's execution ends that panel's sequence and leaves the other two running. - Each run can be **parameterised differently**, because the parameters are captured when that subscriber starts the work. - Cancelling one panel's subscription tears down **only that panel's** execution. - Upstream cost scales with subscribers: n subscribers means n executions of whatever the source does, including any side effect it performs. That last point is the reason this matters operationally. If the source issues a paid, rate-limited or expensive call, the cost of the page is multiplied by how many panels happen to be open. ## Cold and live side by side | Dimension | Cold source | Live (hot) source | |---|---|---| | When work starts | when a subscriber attaches, again each time | already running, independently of subscribers | | Number of runs | one per subscriber | one, shared by everyone attached | | What a late subscriber sees | the whole sequence, from the start of its own run | what arrives after it attaches | | Cost of one more subscriber | another full execution | fanning the same values out once more | | Per-subscriber parameters | possible, each run captures its own | one sequence, so one set for everyone | | Values while nobody is attached | none are produced | produced and dropped unless something retains them | ## Collapsing three runs into one The conversion from cold to live is additive: you put a **multicasting stage** between the query and the panels. That stage subscribes upstream **once**, keeps the list of attached panels, and forwards each upstream value to every one of them. The panels' code does not change; what changes is that they are now looking at one execution rather than three. The stage needs a policy for when the single upstream run starts and stops. The usual one is **reference counting**: subscribe upstream when the first panel attaches, cancel upstream when the last one detaches. Alternatives are starting eagerly and keeping the run alive whether or not anyone is watching, or keeping it alive for a grace period after the last panel closes. ## What the shared version costs 1. **Shared fate.** One upstream failure or completion ends the sequence for every attached panel at once, instead of for one panel at a time. 2. **One set of parameters.** A shared sequence is one sequence; panels that need different filters need different shared sources, keyed by their parameters. 3. **A lifecycle to own.** Somebody has to decide what happens at zero subscribers, and both answers - stop and restart, or keep running - have a bill. 4. **Missed values.** Anything emitted before a panel attaches is gone unless the stage retains a buffer of recent values, and that buffer costs memory and can hand a panel an old reading with no indication of its age. 5. **Shared mutable state.** The stage holds a subscriber list that is mutated while values are flowing, with the concurrency that implies. ## Diagnosing it without reading the library You can settle the question from the outside: 1. Count upstream executions for one user session and compare with the number of panels opened. Equal counts point at a cold source. 2. Open one more panel and watch the upstream count. If it goes up by exactly one, each subscriber is starting its own run. 3. Close every panel and see whether upstream work stops. If it does, you already have a shared stage with reference counting rather than a cold source. 4. Open a panel long after the others and see whether it receives earlier values. Getting the full sequence again is cold; getting only new values is live. The useful mental habit is to stop asking whether a source is shared and start asking **how many times its work runs for n subscribers**. That number is the property; cold and hot are just names for the two answers.

  • How would you confirm from outside the pipeline that the source is cold, rather than one run being restarted?
    Vary the subscriber count and watch upstream executions. A cold source gives one execution per subscriber, each starting when that subscriber attaches and ending with its own terminal signal, and closing one panel stops only that panel's execution. A single run being restarted shows one execution at a time, with a gap between them.
  • What do the panels lose the moment you collapse the three runs into one shared run?
    Independence. They now share one set of parameters, one failure, one ending and one start time, so a panel can no longer ask for its own filter or survive an upstream error the others hit. They also gain a lifecycle question: what the shared run does when no panel is attached.
  • Two panels need the same query with different date ranges - does one shared source help?
    Not as one source. A shared sequence carries one set of parameters, so different ranges mean different sequences. The usual shape is a small map keyed by the parameters, each key holding its own shared source, so panels asking for the same range share a run and panels asking for different ranges do not.

A cold source is a recipe: every cook who picks it up cooks a separate meal. A live source is a pot already simmering, and whoever walks in gets only what is ladled out from that moment on.

saying these in an interview costs you the question

  • Calls the duplicate executions a caching bug rather than a cold source
  • Believes two subscribers to one cold source receive the same values
  • Expects a second panel to pick up values the first panel already received
  • Thinks a cold source can serve only one subscriber at a time
  • Assumes sharing one run is a flag with no design consequences