Composition

Common shapes for software that changes over time.

These patterns show how to arrange inputs, calculations, joins, and outward effects before worrying about language-specific syntax.

Start Small

A useful first graph can be just a few named inputs, one derived result, and one effect at the edge.

Draw the relationship first

Before choosing helpers or operators, write down what can change, what result depends on it, and where that result leaves the graph. This small shape is enough to make the important relationship inspectable.

  • Name the inputs that can change.
  • Use a focused node for each calculation.
  • Keep outward effects at the edge rather than hiding them inside calculations.

Grow only when the problem grows

Add another node or boundary when it explains a real dependency. The goal is not to turn every function into a box; it is to make the changing relationships easy to follow.

  • Keep straight-line work straight-line.
  • Use a graph where fan-out, fan-in, state, or outside timing creates real complexity.
  • Open your language package when you are ready for runnable syntax.

Wait for Real Inputs

A calculation with several dependencies should normally wait until those dependencies have produced real values.

Why early computation causes trouble

A summary that runs before all of its inputs exist must guess. Those guesses often become placeholder objects, special strings, or empty values that later code accidentally treats as real data.

  • Missing input remains missing.
  • A cached real value can be reused when a new connection opens.
  • Partial-input behavior is an explicit choice, not an accidental first run.

A useful rule of thumb

Let the first normal calculation wait for its declared inputs. If the product truly needs a partial result—such as a form summary while fields are still blank—model that choice directly.

  • Do not invent data just to make a node run.
  • Do not use null or an empty object as a secret startup signal.
  • Handle intentionally partial input where readers can see the policy.

Missing Is Different From Empty

No value yet is not the same as a real value such as null, zero, an empty string, or an empty collection.

Keep the distinction honest

Forms, loaders, joins, and retained views often need to know whether an input has arrived. Preserving that distinction prevents a missing value from being mistaken for a legitimate business value.

  • No emission means the dependency has not produced a value yet.
  • Null or an empty collection can still be valid domain data.
  • Invalidation removes an old value instead of replacing it with a made-up one.

Avoid secret placeholders

A placeholder that exists only to force downstream work hides the true state of the graph. Every later calculation must remember the secret, and inspection becomes less trustworthy.

  • Avoid default objects used only as lifecycle markers.
  • Avoid magic strings such as loading unless they are genuine product data.
  • Make intentional defaults part of the domain rather than part of the machinery.

Rejoin Without Mixed Results

When one input updates several branches that later meet, the join should publish one settled result.

A common shape

A single fact feeds two calculations, and a later summary depends on both. If the summary runs after the first branch but before the second, it briefly combines new information with old information.

  • Declare both branch results as dependencies of the join.
  • Let one wave carry the related change through the graph.
  • Publish after the affected branches have settled.

What you should not have to build

Feature code should not manually trigger the join from every branch or reach sideways into another branch to read its current cache. The graph already knows the relationships and can coordinate them.

  • Keep the join as a calculation over declared inputs.
  • Use batching to group related changes when needed.
  • Keep the resulting topology visible.

Results That Wait for Completion

Some results—such as a final report or collected import—only make sense after their input is complete.

When to use this shape

Use a completion-aware calculation when the product needs a last value, a final summary, a bounded collection, or a report produced after all input has arrived.

  • Treat completion as part of the flow rather than a separate polling condition.
  • Keep error handling near the work that can fail.
  • Release outside resources when the graph no longer needs them.

Keep completion visible

The calculation should still receive declared inputs and publish a declared result. It should not discover completion by polling, peeking into another node, or keeping a hidden subscription alive.

  • Use declared dependencies for input.
  • Use your language package for the exact completion-aware helper.
  • Keep file, network, or worker cleanup at the boundary that owns it.