> For the complete documentation index, see [llms.txt](https://docs.appmixer.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.appmixer.com/getting-started/loops.md).

# Loops

Repeat a part of a flow for every item of a list: the Each component with Sequential turned on and Continue Each at the end of the loop.

## Introduction

A loop runs the same steps for every item of a list: every contact from a search, every row of a spreadsheet, every file in a folder. In Appmixer a loop is built from components of the **Controls** utility module.

The recommended way to build a loop is a [**sequential loop**](/getting-started/loops/sequential-loop.md):

```
Each (Sequential) ──item──▶ step ▶ step ▶ Continue Each
      │
      └──done──▶ steps after the loop (use the collected Result)
```

* **Each** with **Sequential** turned on takes a list and sends one item at a time.
* The steps connected to the **item** port of Each are the loop body. They run once for every item.
* **Continue Each** at the end of the loop body confirms that the item is done, so Each sends the next one. It can also add values from the item to the **Result** of the loop.
* Once all items are done, Each sends **done** with the collected **Result**, one entry per item, in the order of the list.

The items are processed one at a time and in their order, every step in the loop body can use the variables of the components before Each, and you get the results of the whole loop without Storage or Join Each.

![A sequential loop: Continue Each closes the loop body](https://4113719406-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzU8KYStPfR8N1iZXsgad%2Fuploads%2Fgit-blob-7760e27beda205adb0fec61110ecbded795dd571%2Floops-sequential-demo-flow.png?alt=media)

### Other options (advanced)

When the order does not matter and speed does, Each can also run the items differently:

* [**Parallel loop with Join Each**](#parallel-loop-with-join-each) (Sequential off, the default of the component): all items are sent at once and processed in parallel, so they can finish in any order. **Join Each** collects them.
* [**Delayed loop**](/getting-started/loops/delayed-loop.md): the items are sent one after another with a pause between them, for example to stay under an API rate limit.

## The Each component

Set **Field** to a variable that holds a list, for example the `result` of a Find or List component. The list can also be a string with a JSON array. Every message on the **item** port contains:

* **Index**: the position of the item in the list, starting at 0.
* **Value**: the item itself. When the list has a known schema, the Designer also offers the item's properties as separate variables.
* **Items Count**: the number of items in the list.
* **Correlation ID**: the ID of this run of the loop (see below).

Once all items are done, Each sends one message on the **done** port with **Items Count**, **Correlation ID** and **Result**. An empty list sends no item, only **done** with a count of 0.

{% hint style="warning" %}
Without **Sequential**, **done** means "all items have been sent", not "all items have been processed". The loop body may still be working on them, and **Result** is empty. Use a [sequential loop](/getting-started/loops/sequential-loop.md) when the next step has to wait for the whole loop.
{% endhint %}

## How the Correlation ID connects the loop

A flow can run the same loop several times at once. A trigger may fire twice in a second, or an Each may be nested inside another Each. All these runs send their items through the same components, so the messages of different runs are mixed together on the way. The **Correlation ID** is what keeps them apart.

**Each creates one Correlation ID per run.** Every time a list arrives on the **in** port, Each generates a new Correlation ID and puts it in every **item** message and in the **done** message of that run. Two runs never share it. The ID is stable, though: if the engine delivers the same input message again after an error, the loop resumes with the same Correlation ID.

**Continue Each finds the waiting run by the Correlation ID.** In a sequential loop, map the **Correlation ID** of the Each **item** to Continue Each. It tells Continue Each which run of which loop to continue, see [The Correlation ID in a sequential loop](/getting-started/loops/sequential-loop.md#the-correlation-id-in-a-sequential-loop).

**Nested loops.** When a loop body contains another Each, every run of the inner Each gets its own Correlation ID. Pair every Continue Each (or Join Each) with the Each on the same level: the inner one uses the Correlation ID of the inner Each, the outer one the Correlation ID of the outer Each.

## Parallel loop with Join Each

{% hint style="info" %}
This is an advanced option. For most loops, use a [sequential loop](/getting-started/loops/sequential-loop.md): it keeps the order and collects the results without Join Each.
{% endhint %}

With **Sequential** off, Each sends all items at once. To wait until the loop body has processed all of them and get them as one list, add **Join Each**. It has two input ports:

* **in**: connect the last step of the loop body. Every message that arrives here is one processed item.
* **count**: connect the **done** port of Each and map its **Items Count** to **Number of input messages**. This tells Join Each how many messages to wait for.

When the given number of messages has arrived, Join Each sends them all as **Array of items** on its **out** port.

![A parallel loop with Each and Join Each](https://4113719406-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzU8KYStPfR8N1iZXsgad%2Fuploads%2Fgit-blob-0832c5ee027fe68ee4af87ced1d6065a40198501%2Floops-each-join-each-flow.png?alt=media)

![Join Each: both ports use the Correlation ID of the same Each](https://4113719406-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzU8KYStPfR8N1iZXsgad%2Fuploads%2Fgit-blob-20e30e123056cbca89e537b0c130982a77f7e10e%2Floops-join-each-inspector.png?alt=media)

If some items never arrive (a step failed, or a Condition in the loop body filtered them out), Join Each would wait forever. That is why **Timeout** is on by default: after **Timeout (seconds)** (300 by default) Join Each sends what has arrived to its **timeout** port, together with the expected count and the number of items that actually arrived.

{% hint style="info" %}
Join Each collects the items in the order in which they **arrive**, not in the order of the list. If the order matters, sort the result by the item's **Index**, or use a [sequential loop](/getting-started/loops/sequential-loop.md).
{% endhint %}

**Join Each groups messages by the Correlation ID.** It keeps a separate counter for every Correlation ID it sees. A message on **in** is added to the group of its Correlation ID, and the count from **done** is set for the group with the same Correlation ID. A group is complete when its number of messages reaches its own count. It does not matter which comes first: the **done** message usually arrives before the loop body has finished, but it can also arrive after it. Both ports of Join Each must therefore get the Correlation ID of the **same** Each:

* On the **in** port, set **Correlation ID** to the **Correlation ID** variable of the Each **item**. The variable is available on the last step of the loop body, because every message in the loop carries the variables of the item it started from.
* On the **count** port, set **Correlation ID** to the **Correlation ID** variable of the Each **done** port.

```
Each ──item──▶ Step A ──▶ Step B ──▶ Join Each (in)     Correlation ID = Each › item › Correlation ID
  └───done───────────────────────────▶ Join Each (count)  Correlation ID = Each › done › Correlation ID
                                                          Number of input messages = Each › done › Items Count
```

Mapping the Correlation ID of an outer Each into the Join Each of an inner loop would merge the results of all the inner loops into one group that never reaches its count.

### Demo flow

The demo flow loops over the list `[1500, 100, 800]` in parallel. The **Slow step** (a Code Block) takes as many milliseconds as the item says, and Join Each collects the results. The items finish, and arrive at Join Each, in the order 100, 800, 1500:

```json
{
    "items": [
        { "result": "item 100 ms" },
        { "result": "item 800 ms" },
        { "result": "item 1500 ms" }
    ]
}
```

Create the flow with the [`POST /flows`](/api/flows.md) endpoint, or with the Appmixer CLI (`appmixer flow create demo-each-join.json`).

<details>

<summary>demo-each-join.json</summary>

```json
{
    "name": "Demo - Each + Join Each",
    "flow": {
        "e2b0c002-0000-4000-8000-000000000001": {
            "type": "appmixer.utils.controls.OnStart",
            "x": 64,
            "y": 64,
            "version": "1.0.0",
            "source": {},
            "config": {},
            "errorHandling": {
                "autoRetry": false,
                "onError": "stopFlow"
            }
        },
        "e2b0c002-0000-4000-8000-000000000002": {
            "type": "appmixer.utils.controls.SetVariable",
            "label": "Items",
            "x": 272,
            "y": 64,
            "version": "1.0.0",
            "source": {
                "in": {
                    "e2b0c002-0000-4000-8000-000000000001": [
                        "out"
                    ]
                }
            },
            "config": {
                "transform": {
                    "in": {
                        "e2b0c002-0000-4000-8000-000000000001": {
                            "out": {
                                "type": "json2new",
                                "modifiers": {
                                    "variables": {}
                                },
                                "lambda": {
                                    "variables": {
                                        "ADD": [
                                            {
                                                "type": "text",
                                                "name": "durations",
                                                "text": "[1500, 100, 800]"
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "errorHandling": {
                "autoRetry": false,
                "onError": "stopFlow"
            }
        },
        "e2b0c002-0000-4000-8000-000000000003": {
            "type": "appmixer.utils.controls.Each",
            "x": 480,
            "y": 64,
            "version": "1.5.0",
            "source": {
                "in": {
                    "e2b0c002-0000-4000-8000-000000000002": [
                        "out"
                    ]
                }
            },
            "config": {
                "transform": {
                    "in": {
                        "e2b0c002-0000-4000-8000-000000000002": {
                            "out": {
                                "type": "json2new",
                                "modifiers": {
                                    "list": {
                                        "b1000000-0000-4000-8000-000000000001": {
                                            "variable": "$.e2b0c002-0000-4000-8000-000000000002.out.durations",
                                            "functions": []
                                        }
                                    }
                                },
                                "lambda": {
                                    "list": "{{{b1000000-0000-4000-8000-000000000001}}}"
                                }
                            }
                        }
                    }
                }
            },
            "errorHandling": {
                "autoRetry": false,
                "onError": "stopFlow"
            }
        },
        "e2b0c002-0000-4000-8000-000000000004": {
            "type": "appmixer.utils.controls.CodeBlock",
            "label": "Slow step",
            "x": 688,
            "y": 64,
            "version": "1.0.0",
            "source": {
                "in": {
                    "e2b0c002-0000-4000-8000-000000000003": [
                        "item"
                    ]
                }
            },
            "config": {
                "transform": {
                    "in": {
                        "e2b0c002-0000-4000-8000-000000000003": {
                            "item": {
                                "type": "json2new",
                                "modifiers": {
                                    "code": {},
                                    "variables": {
                                        "b1000000-0000-4000-8000-000000000002": {
                                            "variable": "$.e2b0c002-0000-4000-8000-000000000003.item.value",
                                            "functions": []
                                        }
                                    }
                                },
                                "lambda": {
                                    "code": "(function() { var end = Date.now() + Number($data.ms); while (Date.now() < end) {} return 'item ' + $data.ms + ' ms'; })()",
                                    "variables": {
                                        "ADD": [
                                            {
                                                "type": "text",
                                                "name": "ms",
                                                "text": "{{{b1000000-0000-4000-8000-000000000002}}}"
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "errorHandling": {
                "autoRetry": false,
                "onError": "stopFlow"
            }
        },
        "e2b0c002-0000-4000-8000-000000000005": {
            "type": "appmixer.utils.controls.JoinEach",
            "x": 928,
            "y": 224,
            "version": "1.1.2",
            "source": {
                "in": {
                    "e2b0c002-0000-4000-8000-000000000004": [
                        "out"
                    ]
                },
                "count": {
                    "e2b0c002-0000-4000-8000-000000000003": [
                        "done"
                    ]
                }
            },
            "config": {
                "transform": {
                    "in": {
                        "e2b0c002-0000-4000-8000-000000000004": {
                            "out": {
                                "type": "json2new",
                                "modifiers": {
                                    "correlationId": {
                                        "b1000000-0000-4000-8000-000000000003": {
                                            "variable": "$.e2b0c002-0000-4000-8000-000000000003.item.correlationId",
                                            "functions": []
                                        }
                                    }
                                },
                                "lambda": {
                                    "correlationId": "{{{b1000000-0000-4000-8000-000000000003}}}"
                                }
                            }
                        }
                    },
                    "count": {
                        "e2b0c002-0000-4000-8000-000000000003": {
                            "done": {
                                "type": "json2new",
                                "modifiers": {
                                    "count": {
                                        "b1000000-0000-4000-8000-000000000004": {
                                            "variable": "$.e2b0c002-0000-4000-8000-000000000003.done.count",
                                            "functions": []
                                        }
                                    },
                                    "correlationId": {
                                        "b1000000-0000-4000-8000-000000000005": {
                                            "variable": "$.e2b0c002-0000-4000-8000-000000000003.done.correlationId",
                                            "functions": []
                                        }
                                    },
                                    "timeout": {},
                                    "timeoutValue": {}
                                },
                                "lambda": {
                                    "count": "{{{b1000000-0000-4000-8000-000000000004}}}",
                                    "correlationId": "{{{b1000000-0000-4000-8000-000000000005}}}",
                                    "timeout": true,
                                    "timeoutValue": 300
                                }
                            }
                        }
                    }
                }
            },
            "errorHandling": {
                "autoRetry": false,
                "onError": "stopFlow"
            }
        }
    },
    "notes": {
        "n1": {
            "x": 48,
            "y": -144,
            "width": 560,
            "height": 136,
            "content": "## Each + Join Each\n\n**Each** sends every item of the list to **Slow step** at once. **Join Each** waits until all of them are processed and sends them as one list.\n\nBoth Join Each ports use the **Correlation ID** of the same Each, so parallel runs of this flow never mix."
        },
        "n2": {
            "x": 48,
            "y": 272,
            "width": 520,
            "height": 150,
            "content": "## Join Each mapping\n\n- **in** - Correlation ID: *Each › item › Correlation ID*\n- **count** - Number of input messages: *Each › done › Items Count*\n- **count** - Correlation ID: *Each › done › Correlation ID*\n\nThe items arrive in the order they finish: 100, 800, 1500."
        }
    }
}
```

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.appmixer.com/getting-started/loops.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
