> ## Documentation Index
> Fetch the complete documentation index at: https://b01t.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Control Flow: repeat, for_each, if_then

> Reference for b01t's build-time control flow: repeat and for_each unroll at build time, while if_then emits adaptive branching for @adaptive programs only.

b01t provides three control flow combinators for structuring programs. `repeat` and `for_each` unroll at build time — they emit gates directly by calling your body function multiple times during the `.build()` call, so the resulting IR contains only flat gate sequences, not loops. `if_then` is different: it emits a genuine conditional branch in the IR and is only valid inside `@adaptive` programs. Import all three from `b01t`.

```python theme={null}
from b01t import repeat, for_each, if_then
```

<Warning>
  `repeat`, `for_each`, and `if_then` are only valid inside a `.build()` call on a `@parametric` or `@adaptive` function. They are not available in exact programs (`@coherent`, `@primitive`).
</Warning>

***

## repeat

```python theme={null}
repeat(count: int, body: Callable[[], None]) -> None
```

Calls `body` exactly `count` times at build time, unrolling the loop into the IR. Use it to apply the same gate pattern multiple times without writing it out manually.

<ParamField path="count" type="int" required>
  Number of times to call `body`. Must be a non-negative integer known at build time.
</ParamField>

<ParamField path="body" type="Callable[[], None]" required>
  A zero-argument callable that emits one iteration of gates.
</ParamField>

```python theme={null}
from b01t import parametric, QReg, h, rz, cx
from b01t import repeat
import math

@parametric
def power_of_two_layers(q: QReg) -> None:
    repeat(4, lambda: (h(q[0]), rz(math.pi / 8, q[0])))

prog = power_of_two_layers.build(("q", 1))
# prog.ops contains 8 GateOps (4 × h + 4 × rz)
```

***

## for\_each

```python theme={null}
for_each(data: Sequence[Any], body: Callable[[int, Any], None]) -> None
```

Iterates over `data` at build time, calling `body(index, value)` for each element. Use it to apply parametric gates driven by classical data without runtime overhead.

<ParamField path="data" type="Sequence[Any]" required>
  A sequence of values to iterate over. The sequence is consumed entirely at build time.
</ParamField>

<ParamField path="body" type="Callable[[int, Any], None]" required>
  A callable that receives `(index, value)` and emits gates for that element.
</ParamField>

```python theme={null}
from b01t import parametric, QReg, rz
from b01t import for_each
import math

angles = [math.pi / 4, math.pi / 3, math.pi / 6]

@parametric
def angle_sweep(q: QReg) -> None:
    for_each(angles, lambda i, theta: rz(theta, q[i % len(q)]))

prog = angle_sweep.build(("q", 3))
# Emits three rz gates with the three angles
```

***

## if\_then

```python theme={null}
if_then(
    cond: Any,
    then_body: Callable[[], None],
    else_body: Optional[Callable[[], None]] = None,
) -> None
```

Emits a conditional branch in the IR. Both `then_body` and `else_body` are captured as op sequences at build time and stored in an `IfOp`. At runtime (when lowered to hardware), the condition is evaluated and the appropriate branch is executed.

**`if_then` is only valid in `@adaptive` programs.** Calling it inside a `@coherent` or `@parametric` function raises `DSLValidationError`.

<ParamField path="cond" type="Any" required>
  The classical condition to branch on. Typically the result of a `measure` call (a string key like `"m_q_0"`). The condition is stored as-is in the IR and evaluated by the backend.
</ParamField>

<ParamField path="then_body" type="Callable[[], None]" required>
  A zero-argument callable that emits gates for the true branch.
</ParamField>

<ParamField path="else_body" type="Callable[[], None]" default="None">
  A zero-argument callable that emits gates for the false branch. If omitted, the false branch is empty.
</ParamField>

```python theme={null}
from b01t import adaptive, QReg, h, x, measure
from b01t import if_then

@adaptive
def teleport_correction(ancilla: QReg, target: QReg) -> None:
    h(ancilla[0])
    m = measure(ancilla[0])
    if_then(
        m,
        lambda: x(target[0]),      # apply X if measurement = 1
        lambda: x(target[0]),      # else branch (here, same for illustration)
    )

prog = teleport_correction.build(("ancilla", 1), ("target", 1))
print(prog.effect)  # Effect.ADAPTIVE
```

**With only a `then` branch:**

```python theme={null}
from b01t import adaptive, QReg, h, z, measure
from b01t import if_then

@adaptive
def phase_correction(q: QReg, out: QReg) -> None:
    m = measure(q[0])
    if_then(m, lambda: z(out[0]))  # apply Z only when m is truthy

prog = phase_correction.build(("q", 1), ("out", 1))
```

<Note>
  The `IfOp` emitted by `if_then` is not supported by `QiskitBackend` in the current release. Use `if_then` to model the semantics in the IR; lowering to hardware requires a custom backend that handles classical feed-forward.
</Note>
