Parallel Processing

Type a port as a list and you get parallelism for free.

This is the single most powerful feature in Weft. You declare types, the compiler figures out where parallelism belongs, the executor runs the lanes. No loop syntax, no async/await, no map/reduce, no annotation.

The core idea

You have a list of articles. You want to summarize each one with an LLM. The LLM takes a single String. You have a List[String]. Weft sees the type mismatch on the edge and splits the list into one parallel lane per item. Each lane runs the LLM independently. When the results reach a downstream port that expects List[String], the lanes are collected back into a list, in the original order.

That is the whole thing. No ceremony.

Expand and gather

Two things happen at the edges of the parallel region:

  • Expand: List[T] flows into T. The compiler splits the list into lanes, one per item.
  • Gather: T flows into List[T]. The compiler collects the lanes back into a list.

Between expand and gather, everything runs in parallel. If no downstream port ever collects the lanes, they just stay parallel all the way to the end.

A full example

The group's input is items: List[String]. Inside, summarizer.prompt expects String. The compiler sees List[String] flowing into String and expands: one LLM call per item, in parallel.

The output summarizer.response is String (one per lane). The group output summaries is List[String], so the compiler gathers back into a list.

Broadcasting

In the example above, llm_config is a single value, not a list. But it flows into every parallel lane. This is called broadcasting: any value at depth 0 is automatically shared across all lanes. You do not need to duplicate configuration or do anything special.

Pairing multiple lists

When two list inputs feed into the same node, they are paired by index. Element 0 of each list goes to the first lane, element 1 to the second, and so on.

If the two lists are different lengths, the compiler throws a shape mismatch error. This catches a whole class of alignment bugs before runtime.

Nested parallelism

The same rule applies recursively. List[List[String]] flowing into String is a two-level split: the outer list is expanded first, then each inner list is expanded inside its lane. The executor handles as many levels of nesting as your types describe.

You rarely write deeply nested parallelism by hand, but when you do, the language does not fight you.

Null lanes

Some lanes may fail or be skipped. When the results are gathered back into a list, the nulls are preserved to keep index alignment intact. The gathered type becomes List[String | Null], not List[String].

Downstream code has to handle that:

If you declare the downstream port as plain List[String], you will get nulls in that list at runtime whenever a lane was skipped. The type system does not automatically widen for you, so either declare the downstream port as List[String | Null] and filter in code, or make sure upstream lanes cannot be skipped (use optional ports where appropriate or a Gate to drop nulls explicitly).

Why this is the best part

In every other language, "do this work in parallel" means scheduling, async/await, a worker pool, shared state worries, cancellation logic, and error handling that differs from the single-item case. In Weft, it is a type on a port.

This is what happens when you push the hard stuff into the compiler. Types describe shape, and shape determines execution. Parallelism becomes a property of the types, not a feature you opt into per-node.

What's next

  • Null propagation: how skipped lanes become null in the gathered result.
  • Types: the types that drive parallelism.
  • Groups: the natural unit for a parallel region.