The Quiet Power of Deferring Work Until You Know What to Do Next

Kai Nguyen

Hatched by Kai Nguyen

Jul 22, 2026

9 min read

86%

0

What if speed comes from knowing when not to act?

Most people think performance is about doing things sooner. Load faster. Run earlier. Start immediately. But some of the most elegant systems in computing succeed for the opposite reason: they pause on purpose. They suspend execution, preserve state, and resume only when the next right step is available.

That idea appears in two places that seem unrelated at first. In programming, a coroutine is a component whose execution can be suspended and resumed, like a function you can pause and continue later. In web development, async and defer are both ways of letting scripts arrive without stopping the browser cold, yet they differ in a crucial way: one allows execution as soon as the file is ready, the other waits until the document is fully parsed.

The deeper lesson is not about syntax. It is about control over timing. The best systems are not always the ones that do work fastest. They are the ones that do work at the right moment, in the right order, without blocking everything else.

That principle reaches far beyond code. It is a useful mental model for how to design software, structure collaboration, and even manage your own attention.


The hidden cost of acting too early

Blocking is the enemy of flow. When a browser encounters a script that must execute immediately, it cannot continue parsing the page until that script is fetched and run. One decision, made too early, can stall the entire experience. This is why loading strategy matters: async tells the browser to fetch in parallel, while defer goes further and says, in effect, “Yes, fetch now, but do not interrupt the page’s construction until the page is ready.”

That distinction captures a powerful truth: availability is not the same as readiness.

A script can be present before the DOM is ready to use. A team member can be available before the dependencies they need are prepared. A thought can arrive before you have enough context to act on it wisely. In all these cases, immediate execution creates friction instead of value.

Coroutines make the same point at a lower level. Unlike ordinary subroutines, which begin at the start and finish once, a coroutine keeps its local state between suspensions. It does not throw away progress just because it pauses. That is a radical design choice. It says, “You may stop me, but do not make me forget where I was.”

The real enemy is not delay. The real enemy is wasted interruption.

This is why coroutines are often described as functions you can pause. The phrase sounds simple, but it contains a philosophy of computation: preserve context, minimize blockage, and resume only when the system has more useful information.


Coroutine thinking: work as a conversation, not a monologue

Traditional functions feel like monologues. They begin, run to completion, and return a result. Coroutines feel more like conversations. One participant speaks, then yields the floor, then speaks again later with more context. That makes them well suited to state machines, pipelines, and cooperative multitasking, where many tasks must make progress without stepping on one another.

The key difference is cooperation. Threads are often preemptive, which means the system can interrupt them at almost any point. Coroutines, by contrast, voluntarily yield control. That voluntary yielding changes the shape of the whole system. It encourages code that is explicit about when it can pause, what state must persist, and what work should wait.

This matters because every complex process has hidden coordination costs. If too many actors insist on immediate priority, the result is congestion. But if actors know when to yield, the system can remain responsive without becoming chaotic.

Think of a restaurant kitchen. A line cook who insists on finishing every dish before acknowledging the next order creates delays. A better kitchen works like a well designed coroutine system. Each station completes a step, hands off at the right time, and resumes when the next ingredient, pan, or instruction is ready. Nothing is forced to finish in isolation. Progress is distributed through careful timing.

This is also why generators are such a useful subset of coroutines. They turn long, potentially expensive processes into sequences of resumable steps. Instead of demanding the whole result at once, they let the consumer pull what is needed, when it is needed. That changes not only efficiency, but the very relationship between producer and consumer.

Once you see this pattern, you notice it everywhere. Great systems often do not eliminate waiting. They architect waiting.


async and defer are really two theories of attention

At first glance, async and defer seem like minor performance tweaks. In reality, they embody two different philosophies of attention and dependency management.

With async, the browser downloads the script without blocking HTML parsing, and then executes it as soon as it is ready. This is perfect when the script is independent, when exact ordering does not matter, or when the code can stand alone. It is a strategy for opportunistic execution.

With defer, the browser also downloads the script without blocking parsing, but delays execution until after the HTML is fully parsed. This is ideal when the script depends on the document structure being complete. It is a strategy for sequenced readiness.

The choice is not just technical. It encodes a judgment about dependencies.

If you execute too early, you risk acting on partial reality. If you wait too long, you waste the opportunity to overlap work. The art is to distinguish between tasks that can proceed independently and tasks that require a stable context. Good systems do this instinctively. Bad systems treat every action as urgent, then wonder why everything feels slow.

This is where coroutines and script loading meet conceptually. Both ask the same question: What should keep its state, and what should yield? A coroutine suspends itself while preserving context. defer preserves the page’s evolving structure until the right moment to run the code. Both are forms of respectful waiting.

A useful way to think about this is to separate work into three categories:

  1. Independent work, which can begin immediately and finish on its own.
  2. Context dependent work, which can be prepared early but should wait to execute.
  3. Stateful work, which must pause and resume without losing its place.

Once you classify tasks this way, many bottlenecks become obvious. Not everything should be a foreground priority. Not everything should be serialized. And not everything should be preempted by the nearest available moment.


A mental model: the economy of interruption

The deepest connection between coroutines and script loading is not suspension itself, but the economy of interruption.

Interruption is costly because it fragments state. It forces systems to reorient, reload context, or postpone something else. A browser blocked by a script cannot continue building the page. A process that cannot yield must either hog resources or be cut off abruptly. A person who switches tasks too often loses the thread of their own thinking.

Coroutines solve this by making interruption explicit and cheap. Instead of being forcibly torn away, they agree in advance where pausing is safe. That means local state persists naturally, and resumption becomes a continuation rather than a reconstruction.

This is a useful metaphor for knowledge work. Many teams operate as if every message demands an immediate answer. The result is a kind of preemptive multitasking for humans, where attention gets interrupted before it can accumulate into insight. A coroutine mindset says something different: define deliberate yield points. Protect the context that matters. Resume with continuity rather than confusion.

The same logic applies to web performance. A script that blocks parsing is like a colleague who insists everyone stop until they have finished speaking. defer behaves more like a disciplined teammate who waits until the room is ready, then speaks in the right order. The page gets built, and the script still runs at the correct time.

Maturity in systems design often looks like restraint.

That is the counterintuitive insight. The more sophisticated the architecture, the less it tries to force immediate action everywhere. It creates clear rules for pausing, resuming, and ordering. It treats timing as a first class design variable, not an afterthought.


Why this matters beyond code

The coroutine model offers a better way to think about many problems that seem unrelated to programming.

In writing, you often need to suspend an idea before it is fully complete, preserve the thread, and return after more evidence emerges. In meetings, a useful discussion is not one where everyone speaks continuously, but one where people know when to hold a point and when to return to it later. In personal productivity, the best days are not always the ones with the most effort, but the ones with the fewest self inflicted interruptions.

There is a common mistake in all these domains: assuming that progress requires uninterrupted forward motion. In reality, progress often depends on well designed pauses.

Imagine building a house. You would not pour the roof before the foundation is set, just because concrete is available. You would not install windows before the framing is stable. The sequence matters. Not all readiness is visible from the outside, and not all delay is inefficiency. Some delays are coordination.

That is what defer understands. It delays execution not because the work is unimportant, but because the context must be ready. That is what coroutines understand too. They pause not to abandon work, but to preserve continuity while other parts of the system catch up.

If you adopt this perspective, you stop asking only, “How do I make this faster?” and start asking, “What should be allowed to wait, and what must remain in memory until the moment is right?” That question is the basis of robust design.


Key Takeaways

  • Treat timing as architecture. Ask whether a task should run now, later, or in stages. Many performance and coordination problems are really timing problems.
  • Separate readiness from availability. Something can be present before it is safe or useful to act on it.
  • Use explicit yield points. In systems and in work habits, deliberate pauses preserve context and reduce wasteful interruption.
  • Prefer deferred execution when context matters. If a task depends on a stable environment, wait until that environment is complete.
  • Design for cooperation, not just speed. Systems that share control well often outperform systems that fight for immediate priority.

The real lesson: good systems know how to wait

The surprise hidden inside coroutines and defer is that delay is not the opposite of progress. Sometimes delay is what makes progress coherent. A coroutine does not lose its identity when it yields. A deferred script does not become less important because it waits for the page to finish. In both cases, the system is doing something subtle and powerful: preserving the right state until the right moment.

That is a much richer idea than “do things later.” It is about earning the right to act. It is about matching work to context, and context to timing, so that execution becomes not merely fast, but correct, graceful, and resilient.

The next time you are tempted to optimize for immediacy, ask a better question: what if the smartest move is not to run now, but to pause with precision? In software, in teams, and in life, the systems that endure are often the ones that know exactly when to resume.

Sources

← Back to Library

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 🐣