While Loop End
This is the node that runs your loop, and one bad wire makes it run forever
- flow
- initial_value0
- initial_value1
- initial_value2
- initial_value3
- initial_value4
- initial_value5
- initial_value6
- initial_value7
- initial_value8
- initial_value9
- initial_value10
- initial_value11
- initial_value12
- initial_value13
- initial_value14
- initial_value15
- initial_value16
- initial_value17
- initial_value18
- initial_value19
- value0
- value1
- value2
- value3
- value4
- value5
- value6
- value7
- value8
- value9
- value10
- value11
- value12
- value13
- value14
- value15
- value16
- value17
- value18
- value19
What it is
While Loop End is the closing bracket of an open-ended loop: While Loop Start hands values down, your body does something to them, and While Loop End looks at a boolean and decides whether to run the whole region again. Its condition input is the single most important wire in the loop.
Reach for it when the number of iterations depends on what happens inside the loop - keep refining until the mask is empty, keep upscaling until you're past 4K, keep sampling until a downstream node says good enough. A count-based for loop can't do that; a while loop can.
ComfyUI has no native loops at all, so what this node does is fake it through dynamic prompt expansion. Knowing the mechanic is the difference between using these nodes confidently and joining the queue of people who say loop nodes don't work.
How it works
Each time While Loop End executes, one of two things happens.
condition is False → it returns the values it received. The loop is over, and its valueN outputs are the final state, ready to feed whatever comes after the loop.
condition is True → it goes to work. Using GraphBuilder, it walks backwards from itself to collect every node that feeds it, then collects everything downstream of the While Loop Start it's wired to, intersects the two, and copies that whole region into a new pass. The copy gets the current values pushed into its While Loop Start's value sockets, the copy's End is re-internally-named Recurse and becomes the next round's decision point. Your original nodes don't re-run - a duplicate of the region does, with new inputs.
This is why loop nodes in ComfyUI feel uncanny the first time you watch them: the canvas shows one block, and the queue shows it executing twenty times.
Sockets
- flow (WFLOW_CONTROL, required) - from While Loop Start. Internally this is a
rawLinkinput, meaning it carries the connection itself rather than a value; that's how End finds the Start it belongs to. - condition (BOOLEAN, required) - loop again (True) or stop (False). Wire it to the output of whatever comparison or state check you built. A Loop Step node's
continueoutput is the canonical source. - initial_value0 … initial_value19 (optional wildcards) - what you hand forward. These become the next round's Start values. If you want a counter to advance, wire the incremented counter here; if you want an image to feed itself back in, wire the image here.
- value0 … value19 (outputs) - the carried values when the loop finishes.
Install
ComfyUI Manager, search ComfyUI-Practical-Tools, or:
cd ComfyUI/custom_nodes
git clone https://github.com/wenchengxiang/ComfyUI-Practical-Tools
Restart. Pure Python, no dependencies, no models for this node. The pack's requirements.txt (onnxruntime, nvidia-vfx, openai) belongs to its tagger, RTX-upscale and API nodes, and the pack loads each Python file in its own try/except, so one broken dependency prints a [WCX Nodes Error] line and leaves everything else - loops included - working.
Traps
Nothing stops a True that stays True. There's no iteration cap in this node and none in the engine that you want to rely on. An infinite loop eats RAM as it expands and you'll be killing the process. Rule of thumb: never test a new loop with an unbounded condition - start with a counter and a small total, prove the wiring, then swap in the real test.
Unwired inputs arrive as nothing on the next round. The End passes whatever it has forward, including None for any initial_valueN you left empty. That's usually fine (the loop body often only cares about one or two slots) but it's a silent way to lose a value between rounds: if your accumulator resets to empty each time, check that it's actually wired into the End.
The loop body is defined by wires, not by position. It's discovered by dependency walk - nodes that feed this End's inputs, reachable from the Start. A node in the visual grouping that doesn't feed End is outside the loop.
Output nodes inside the loop fire every round, deliberately. Preview Image and Save Image nodes are explicitly kept in the copy, including when they live inside a subgraph, so a loop that saves is a loop that writes one file per iteration. Helpful when you want to watch progress; surprising when you don't.
Nested loops are supported but hard to debug. The dependency walk deliberately skips other loop-end nodes when mapping the region, so an inner Start/End pair inside an outer loop works - but when something is wrong, the console is your only diagnostic, so keep nesting to one level while you're building.
Runtime is a multiplier, not a constant. Everything in the body re-executes per round, so however many iterations your condition allows is how many times your sampler, decode and upscaler run. If a loop feels far slower than you expected, look at what you put inside it - a full VAE decode per iteration is the usual culprit, and moving it after the loop is often free.
Inputs (22)
| Name | Type | Default | Description |
|---|---|---|---|
| flow | WFLOW_CONTROL | — | |
| condition | BOOLEAN | — | |
| initial_value0opt | * | — | |
| initial_value1opt | * | — | |
| initial_value2opt | * | — | |
| initial_value3opt | * | — | |
| initial_value4opt | * | — | |
| initial_value5opt | * | — | |
| initial_value6opt | * | — | |
| initial_value7opt | * | — | |
| initial_value8opt | * | — | |
| initial_value9opt | * | — | |
| initial_value10opt | * | — | |
| initial_value11opt | * | — | |
| initial_value12opt | * | — | |
| initial_value13opt | * | — | |
| initial_value14opt | * | — | |
| initial_value15opt | * | — | |
| initial_value16opt | * | — | |
| initial_value17opt | * | — | |
| initial_value18opt | * | — | |
| initial_value19opt | * | — |
Outputs (20)
| Name | Type | Description |
|---|---|---|
| value0 | * | — |
| value1 | * | — |
| value2 | * | — |
| value3 | * | — |
| value4 | * | — |
| value5 | * | — |
| value6 | * | — |
| value7 | * | — |
| value8 | * | — |
| value9 | * | — |
| value10 | * | — |
| value11 | * | — |
| value12 | * | — |
| value13 | * | — |
| value14 | * | — |
| value15 | * | — |
| value16 | * | — |
| value17 | * | — |
| value18 | * | — |
| value19 | * | — |