The Hidden Continuation Inside Every Query
Hatched by Kai Nguyen
Sep 04, 2026
11 min read
0 views
88%
What if a database query and a paused function are not opposites, but two views of the same idea?
One appears static: a SQL statement describes values to select, compare, transform, and rename. The other appears dynamic: a coroutine runs, stops, remembers where it was, and continues later. Yet both confront the same fundamental problem: how can a computation preserve meaning while moving through time?
This question matters far beyond database syntax or programming language theory. It shapes how we design data pipelines, APIs, user interfaces, event systems, and even our own analytical habits. The deepest connection between expressions and coroutines is that both turn a process into something that can be handled indirectly. An expression packages computation into a value. A coroutine packages progress into a resumable state.
The result is a useful design principle:
Good systems do not merely compute answers. They make the intermediate state of computation explicit, inspectable, and reusable.
SQL teaches us to ask what a value means in the current row. Coroutines teach us to ask what execution means at the current pause. Together, they reveal a general architecture for building systems that remain understandable as complexity grows.
From Values to Pauses
An SQL expression is anything that can be evaluated to produce a value. A column reference produces the value stored in a column for the current row. A comparison produces a true or false result. A function call produces whatever its function returns. Concatenating two strings produces a new string. Even a pattern such as name LIKE 'A%' is a compact description of a computation whose result can guide what happens next.
This sounds elementary, but it contains a powerful abstraction. The expression does not need to explain every internal step. It gives the database a meaningful unit of work: evaluate this relationship, produce this value, and use it in the surrounding operation.
Consider:
SELECT
first_name || ' ' || last_name AS full_name,
salary * 1.1 AS adjusted_salary
FROM employees
WHERE salary BETWEEN 50000 AND 90000;
Several things are happening here. A row supplies context. Expressions read that context, calculate new values, and produce an output with a more useful name. The query is not merely a list of commands. It is a network of value producing relationships.
A coroutine packages a different kind of thing. It does not primarily package a value. It packages the ability to continue. When execution leaves a coroutine, its local data remains available. When it resumes, it does not begin again at the top. It returns to the point where it paused, carrying its prior state with it.
Imagine a function that yields one item at a time from a large file. A normal subroutine might read the entire file, return a collection, and disappear. A coroutine reads one item, pauses, and later resumes with its file position, counters, and local variables intact. The state is not an incidental implementation detail. It is the identity of the computation.
This creates a useful pairing:
- An expression captures what a computation means.
- A coroutine captures where a computation currently is.
The first abstracts over instructions by representing a result. The second abstracts over instructions by representing continuation. One gives us a value at a point in data. The other gives us a position in a process.
The Common Design Problem: Context
The apparent gap between SQL and coroutines closes when we focus on context.
A SQL expression never floats in isolation. The meaning of a column reference depends on the current row. The meaning of a comparison depends on the values being compared. A wildcard such as % means any sequence of characters, but only within a pattern matching operation. The same symbol can be meaningless or meaningful depending on the surrounding context.
A coroutine behaves similarly. The significance of a local variable depends on the point at which execution paused. A counter holding the number 10 is not enough to describe the state of a generator. We also need to know what it has already yielded, what it is waiting for, and which instruction will run next.
In both cases, computation is not simply a function from input to output. It is a function from context to meaning.
This is why names matter so much. SQL's AS keyword can rename an output column from an opaque calculation into a meaningful concept:
SELECT price * quantity AS order_total
FROM order_items;
The alias does not change the arithmetic. It changes the way later readers can reason about the result. It turns an anonymous value into a named piece of the model.
Coroutines need an equivalent discipline. A paused computation should expose meaningful state rather than a mysterious collection of internal variables. A streaming parser might preserve states called reading_header, reading_body, and complete, rather than merely storing an instruction pointer and a buffer. Naming the state makes the continuation legible.
This suggests a broader rule:
Abstraction becomes reliable when both the result and the context that gives it meaning are named.
A query with carefully named derived columns is easier to maintain. A coroutine with carefully named states is easier to resume, test, and debug. In both settings, names function as handles for thought.
Declarative Selection and Cooperative Control
SQL and coroutines also illuminate two complementary approaches to control.
SQL is largely declarative. You specify relationships among values and conditions under which rows belong in the result. You do not usually instruct the database to inspect the first row, then the second row, then the third. You describe the desired transformation, and the database determines how to execute it.
Coroutines are operational. They let a programmer describe a sequence that advances, pauses, and resumes. The order of events is part of the design. A coroutine might wait for input, yield a response, wait again, and preserve its state across every transition.
It is tempting to treat these as competing philosophies. Declarative systems hide control flow. Coroutines expose it. Yet robust software often needs both.
Suppose an application processes customer events. SQL can express a filter such as:
SELECT customer_id, event_type
FROM events
WHERE event_type LIKE 'purchase%';
The query expresses which records matter. A coroutine can then manage what happens over time: wait for a batch, process each matching event, pause when a downstream service is busy, and resume without losing its position.
The query answers, which values belong to this computation? The coroutine answers, how should the computation proceed as the world changes?
This division is more than convenient. It keeps two kinds of complexity from contaminating each other. Data selection is governed by predicates, comparisons, patterns, and transformations. Temporal coordination is governed by suspension, resumption, and state transitions.
When those concerns are mixed carelessly, systems become brittle. A data query that embeds too much procedural behavior becomes difficult to optimize and reason about. A coroutine that embeds ad hoc data interpretation becomes difficult to test because its control state and business rules are tangled together.
A useful architecture therefore has two layers:
- A value layer, where expressions define and transform data.
- A continuation layer, where coroutines manage waiting, sequencing, and resumption.
The layers communicate through explicit values. That boundary is crucial. If a coroutine yields a clearly named result, another component can consume it without knowing how the result was produced. If a query returns a well defined stream of records, a coroutine can coordinate their processing without knowing how the database found them.
The most scalable systems are often built from exactly this separation: pure descriptions of what should count, paired with resumable mechanisms for when work should happen.
The Hidden Cost of Invisible State
The strongest connection between these ideas appears when something goes wrong.
A SQL expression can be locally understandable but globally misleading. A calculation may be correct for each row while its alias hides an important distinction. A wildcard may match more records than intended. A BETWEEN condition may include boundaries the user did not realize were inclusive. The computation is visible, but the assumptions surrounding it may not be.
Coroutines have the opposite danger. Their output may look correct while their internal state is difficult to inspect. A coroutine can pause while holding a resource, waiting for an event that will never arrive, or preserving a stale value from an earlier phase. The problem is not necessarily the next instruction. It is the invisible history that determines which instruction comes next.
These are two versions of the same failure: the system hides the context that controls interpretation.
We often debug by examining outputs, but outputs are only the surface. A more effective method is to inspect the computation's coordinates.
For an expression, ask:
- What is the current row or input context?
- What type of value is being produced?
- What boundaries or matching rules govern the operation?
- What name will later users assign to the result?
For a coroutine, ask:
- What state is it in now?
- What event will allow it to resume?
- What local data survived the last pause?
- What transition is possible next?
This leads to a practical debugging model called state plus meaning. Every intermediate result should be understood along two dimensions:
Meaning: What does this value represent?
State: Under what conditions was it produced, and what happens next?
In a data pipeline, log both the transformed value and the stage that produced it. In a streaming process, record both the event being handled and the named state of the coroutine. In an interface, distinguish the displayed data from the loading, waiting, or retrying state that surrounds it.
The model is especially valuable in asynchronous systems. Concurrency is not parallelism. A group of coroutines may take turns making progress on a single processor without executing simultaneously. This can be efficient because switching need not involve expensive system calls or blocking operations. But cooperative systems place a responsibility on each participant: it must yield control.
The same principle appears in data work. A query can be elegant because it delegates execution to the database engine. But that convenience does not eliminate responsibility. The designer still needs precise expressions, explicit aliases, and carefully tested conditions. Cooperation works only when each component makes its assumptions visible enough for the next component to use.
A General Pattern for Better Systems
The intersection of expressions and coroutines gives us a reusable pattern for designing software and thinking clearly.
1. Represent meaning as values
If a concept will be reused, inspected, tested, or passed between components, represent it explicitly. A derived SQL column named order_total is more useful than an unnamed multiplication. A parser that yields a structured record is more useful than one that mutates hidden global state.
Values create stable boundaries. They allow one component to finish a piece of reasoning and hand the result to another.
2. Represent progress as state
If a process can wait, pause, retry, or continue later, represent its progress explicitly. A state machine is not bureaucratic overhead when the process already has multiple phases. It is a truthful description of reality.
For example, an ingestion coroutine might move through these states:
waiting_for_batch
reading_record
writing_record
waiting_for_confirmation
complete
Each state clarifies what inputs are valid and what transitions can occur. This is safer than letting the process drift through a tangle of callbacks and implicit flags.
3. Keep selection separate from scheduling
Use expressions to describe what should be included, transformed, or compared. Use resumable control structures to describe when work proceeds and what happens when it cannot proceed.
This separation makes optimization possible. A database can optimize a value oriented query. A scheduler can coordinate a stateful worker. Each can improve without rewriting the other.
4. Make boundaries explicit
Comparisons need clear boundaries. Patterns need clear matching rules. Coroutines need clear suspension points. Ambiguity at the boundary is where many bugs begin.
Ask whether an interval includes its endpoints. Ask whether a wildcard can match an empty string. Ask whether a pause can occur while a resource is held. Ask what happens if the expected resume event never arrives.
5. Design for inspection
A computation that cannot be inspected cannot be trusted at scale. Name outputs. Name states. Preserve enough information to reconstruct why a result appeared and why a process is waiting.
This does not mean exposing every implementation detail. It means exposing the details that control interpretation.
Key Takeaways
- Treat expressions as named units of meaning. Give calculated values aliases that explain their role, not merely their formula.
- Treat pauses as data. If a process can stop and resume, define its states and transitions explicitly.
- Separate what from when. Let value oriented logic determine what belongs in a computation, and let resumable control determine when work advances.
- Debug context, not only output. Inspect the current row, matching rules, preserved locals, and next possible transition.
- Make boundaries visible. Clarify comparison limits, pattern behavior, suspension points, and failure paths before they become production bugs.
The surprising lesson is that a query and a coroutine are both forms of controlled incompleteness. An expression does not contain the whole program. It waits for a row, an input, or an enclosing operation to complete its meaning. A coroutine does not complete its work in one uninterrupted movement. It leaves part of the computation suspended for later.
That is not a weakness. It is how complex systems remain composable.
A value is a computation that has finished enough to be shared. A coroutine is a computation that has paused enough to be resumed.
Once we see this, the design question changes. We stop asking only whether a system produces the right answer. We ask whether its meanings are named, whether its pauses are deliberate, and whether another component can pick up the work without guessing what came before.
The best systems are not those that eliminate intermediate states. They are those that make intermediate states intelligible. A query gives computation a vocabulary of values. A coroutine gives it a memory of progress. Together, they point toward a broader ideal: software that can explain not only what it knows, but where it is in the process of finding out.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣