---
title: "Control flow"
description: "Control flow decides what runs on each bar. A wrun indicator is AssemblyScript, so the constructs are the typed C-style ones: if/else, the ? : ternary, switch…"
order: 50
section: "functions"
---

<!-- source: docs/indicators/functions/control-flow.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Control flow

Control flow decides what runs on each bar. A wrun indicator is AssemblyScript,
so the constructs are the typed C-style ones: `if`/`else`, the `? :` ternary,
`switch`, `for`, `while`, `do`/`while`, `break` and `continue`. Everything here
runs inside `onBar()`, which the host calls once per bar as it walks the loaded
history. The bar loop belongs to the host: your code sees one bar per call, and
a loop inside it walks memory you own, never history you do not have.
Module-level variables are where state lives between calls.

| Construct | Use it for |
| --- | --- |
| `if` / `else` | decision logic; the condition must be a `bool` |
| `? :` | one inline choice; both branches share a type |
| `switch` | mapping a discrete code (a mode param) to a value or a branch |
| `for` | a known iteration count: a ring buffer, a cell block, a bounded scan |
| `while`, `do`/`while` | a condition-driven search with a bound you can prove |
| `break`, `continue` | leaving a loop early, skipping an iteration |

## if / else

Branch on any boolean condition; the `else` is optional and `else if` chains
as usual. The condition must be a real `bool`: `if (value)` on a number is a
compile error (`AS200`), so compare (`if (value > 0.0)`, `if (!isNaN(value))`).

```typescript
if (close > open) {
  direction = 1.0; // up bar
} else if (close < open) {
  direction = -1.0; // down bar
} else {
  direction = 0.0; // doji
}
```

A value from the last bar, such as the previous close, is a module-level
variable the module kept from the last call, not `close[1]`
([Execution model](../core-concepts/execution-model.md)).

## Ternary expressions

For a single inline choice, `condition ? a : b` is cleaner than a full
`if`. Both branches must have the same type: write `1.0 : 0.0` for an
`f64`, not `1 : 0`, or the compiler infers an integer and refuses the
assignment. It is the idiomatic way to pick a value or to suppress a
draw, since `NaN` written to an output draws nothing:

```typescript
// Pick a price based on the bar's direction.
const anchor = close > open ? high : low;
// Write the low only on signal bars; NaN draws nothing (a shape_where gate is the declared form).
const signal = crossed == 1 ? low : NaN;
```

## switch

`switch` matches an integer against `case` labels with a `default`
fallback, and falls through without `break` exactly as JavaScript does.
Use it to map a discrete code (a mode param) to a value or a branch.

```typescript
switch (mode) {
  case 0:
    picked = close;
    break;
  case 1:
    picked = high;
    break;
  default:
    picked = low;
}
```

## for loops

Use a C-style `for` when you need a fixed number of iterations: summing a
window, scanning buckets, accumulating a value. The form is
`for (let i = 0; i < end; i++) { ... }`: the counter is an `i32` (`i++` and
`i += 1` both work), so cast it when it meets an `f64`. Loop bodies work with
locals and with the module's buffers. A `StaticArray<f64>` sized from a
param's `max` in `onStart()` or at module start is the usual target, and
`unchecked(ring[i])` skips the bounds check once the index is provably inside.

```typescript
// Sum the last closes by hand, from a ring buffer the module keeps; count is how many slots are filled.
let total = 0.0;
for (let i = 0; i < count; i++) {
  total += unchecked(ring[i]);
}
```

A celled input's block is the other common loop target:
`for (let i = 0; i + 3 < n; i += 4)` walks `[low, high, buy, sell]` tuples
([Order flow](order-flow-kit.md)).

There is no history array to loop over (`close[n]` does not exist): a wrun
indicator has no timeseries. A window is a `History` you `push()` once per
bar and read with `ago(n)`
([Stats, history and lists](stats-history-lists.md)), or by hand a
`StaticArray<f64>` ring the module fills one bar at a time, sized once from
the param's `max` ([Collections](../core-concepts/collections.md)). The ring
is the window, and it is yours to size and index.

## while and do/while

`while (condition) { ... }` and `do { ... } while (condition)`. Use them
when the iteration count depends on what you find, and make the bound
explicit: a search back through a ring stops at the ring's size whatever
the data says. Here `above` is a ring of booleans (was the close above
its average on that bar) and `slot(back)` maps "bars back" to a ring
index.

```typescript
let back = 0;
while (back < count && !unchecked(above[slot(back)])) {
  back += 1;
}
```

A counted `for` is clearer and safer when you know the bound. Reach for
`while` only for genuine search-until-found logic, and make sure the
condition can end.

## break and continue

`break` leaves the loop; `continue` skips to the next iteration. Both
work in `for`, `while`, and `do`/`while`, and both are the idiomatic way
to stop a scan the moment it has its answer.

## Branches in one module

A `for` loop, an `if`/`else` branch, a ternary, and a `switch`, folded
into one value, with a bar counter the module keeps itself (there is no
`barIndex` global). The `if`/`else`, ternary, `switch` and `for` snippets
above appear verbatim inside `onBar()`.

```typescript sample=fn-control-flow
param("mode", 0, { min: 0, max: 2, description: "0 rotate by bar, 1 high, 2 low: which price the switch picks" });
param("window", 5, { min: 2, max: 50, description: "Closes summed by the for loop" });
output("direction", line, lower, { description: "+1 up bar, -1 down bar, 0 doji" });
output("anchor", line, lower, { description: "The high on up bars, the low otherwise" });
output("signal", none, lower, { description: "The low on bullish-cross bars, NaN elsewhere" });
output("total", line, lower, { description: "The last closes summed by hand" });
output("picked", line, lower, { description: "The price the switch picked" });
output("folded", line, lower, { description: "All four results in one number" });

const MAX_WINDOW = 50;
const ring = new StaticArray<f64>(MAX_WINDOW);
let n: i32 = 5;
let cursor: i32 = 0;
let count: i32 = 0;
let modeParam: i32 = 0;
let barIndex: i32 = 0;
let sma = new Sma(5);
const cross = new Cross();

function onStart(): void {
  modeParam = i32(p_mode());
  n = i32(p_window());
  sma = new Sma(n);
}

function onBar(): void {
  const close = bar.close();
  const open = bar.open();
  const high = bar.high();
  const low = bar.low();
  unchecked((ring[cursor] = close));
  cursor = (cursor + 1) % n;
  if (count < n) count += 1;
  const crossed = cross.update(close, sma.update(close));

  let direction = 0.0;
  if (close > open) {
    direction = 1.0; // up bar
  } else if (close < open) {
    direction = -1.0; // down bar
  } else {
    direction = 0.0; // doji
  }

  // Pick a price based on the bar's direction.
  const anchor = close > open ? high : low;
  // Write the low only on signal bars; NaN draws nothing (a shape_where gate is the declared form).
  const signal = crossed == 1 ? low : NaN;

  // Sum the last closes by hand, from a ring buffer the module keeps; count is how many slots are filled.
  let total = 0.0;
  for (let i = 0; i < count; i++) {
    total += unchecked(ring[i]);
  }

  // A per-bar mode: the param, or the bar index modulo 3 when the param is 0.
  const mode = modeParam == 0 ? barIndex % 3 : modeParam;
  let picked = NaN;
  switch (mode) {
    case 0:
      picked = close;
      break;
    case 1:
      picked = high;
      break;
    default:
      picked = low;
  }

  barIndex += 1;
  out_direction(direction);
  out_anchor(anchor);
  out_signal(signal);
  out_total(total);
  out_picked(picked);
  out_folded(anchor + picked / 100.0 + total);
}
```

## Loops in one module

A search-until-found: how many bars back the close last traded above its
average, found with a `while` over a ring buffer, plus a `for` that sums
the ring, a `for` with `continue` that counts up bars while skipping doji
bars, and a `do`/`while` with `break` that finds the first bar back whose
range exceeds the current one. The `for` and `while` snippets above appear
verbatim inside `onBar()`.

```typescript sample=fn-loops
param("period", 20, { min: 2, max: 200, description: "Average window and search depth" });
output("direction", line, lower, { color: "#94a3b8", description: "+1 when the close rose, -1 when it fell, 0 flat" });
output("close_sum", line, lower, { color: "#7c3aed", description: "The window's closes summed by the for loop" });
output("bars_since_above", line, lower, { color: "#2563eb", description: "Bars back to the last close above the average (0 = this bar), NaN if none in the window" });
output("bars_above", line, lower, { color: "#16a34a", description: "Up bars in the window, doji bars skipped" });
output("wider_back", line, lower, { color: "#f97316", description: "Bars back to the first bar with a wider range than this one, NaN if none" });

const MAX_PERIOD = 200;
const ring = new StaticArray<f64>(MAX_PERIOD);
const opens = new StaticArray<f64>(MAX_PERIOD);
const ranges = new StaticArray<f64>(MAX_PERIOD);
const above = new StaticArray<bool>(MAX_PERIOD);
let size: i32 = 20;
let sma = new Sma(20);
let cursor: i32 = 0;
let count: i32 = 0;
let prevClose: f64 = NaN;

// The ring slot `back` bars behind the newest write.
function slot(back: i32): i32 {
  return (cursor - 1 - back + size) % size;
}

function onStart(): void {
  size = i32(p_period());
  sma = new Sma(size);
}

function onBar(): void {
  const close = bar.close();
  const open = bar.open();
  const average = sma.update(close);

  // if / else: the bar's direction against the previous close the module kept.
  let direction = 0.0;
  if (close > prevClose) {
    direction = 1.0;
  } else if (close < prevClose) {
    direction = -1.0;
  }
  prevClose = close;

  unchecked((ring[cursor] = close));
  unchecked((opens[cursor] = open));
  unchecked((ranges[cursor] = bar.high() - bar.low()));
  unchecked((above[cursor] = !isNaN(average) && close > average));
  cursor = (cursor + 1) % size;
  if (count < size) count += 1;
  if (isNaN(average)) return;

  // for: sum the filled slots of the ring.
  let total = 0.0;
  for (let i = 0; i < count; i++) {
    total += unchecked(ring[i]);
  }

  // while: search back until a bar above the average is found, or the window runs out.
  let back = 0;
  while (back < count && !unchecked(above[slot(back)])) {
    back += 1;
  }
  const barsSinceAbove = back < count ? f64(back) : NaN;

  // for with continue: count up bars, skipping doji bars.
  let ups = 0;
  for (let i = 0; i < count; i++) {
    const c = unchecked(ring[i]);
    const o = unchecked(opens[i]);
    if (c == o) continue;
    if (c > o) ups += 1;
  }

  // do/while with break: the first bar back whose range exceeds this one.
  const current = unchecked(ranges[slot(0)]);
  let probe = 1;
  let widerBack = NaN;
  if (count > 1) {
    do {
      if (unchecked(ranges[slot(probe)]) > current) {
        widerBack = f64(probe);
        break;
      }
      probe += 1;
    } while (probe < count);
  }

  out_direction(direction);
  out_close_sum(total);
  out_bars_since_above(barsSinceAbove);
  out_bars_above(f64(ups));
  out_wider_back(widerBack);
}
```

Every loop here is bounded by `count`, which is bounded by `size`, which
is bounded by the param's declared `max`: the module allocates its rings
once for `MAX_PERIOD` and never grows.

## Loop limits

A wrun indicator has no iteration counter: the chart bounds the whole
evaluation with an execution timeout (20 s per run). A module that overruns
it is stopped and its worker replaced, so a runaway loop, or a `while` whose
condition never becomes false, fails the run loudly rather than hanging the
chart. In practice a loop bounded by a param's declared `max` never
approaches the timeout; keep loop bounds tied to declared ranges and the
module stays cheap on a long history
([Execution model](../core-concepts/execution-model.md)).

Loops run once per bar over the loaded history and again each time a live
update re-evaluates the forming bar (at most about once a second). A scan of
a 200-slot ring is cheap; a scan inside a scan is not, so cache a statistic
once per bar and reuse it.

## What to keep in mind

- **Per-bar execution.** `onBar()` re-runs on every bar. To carry a value
  across bars, keep it in a module-level `let`. A `let` inside `onBar()`, a
  loop or a branch is local to that block and gone when the call returns.
- **Typed branches.** Every branch of a ternary and every assignment
  agrees on a type; `f64` values get float literals (`0.0`, `1.0`), counts
  and indexes are `i32`.
