---
title: "Conditionals and loops"
description: "Conditionals and loops control which code runs during each bar's state() call. Use them for decision logic, counted iteration over a buffer, and conditional…"
order: 53
section: "functions"
---

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

# Conditionals and loops

Conditionals and loops control which code runs during each bar's
`state()` call. Use them for decision logic, counted iteration over a
buffer, and conditional searches, keeping in mind that the bar loop
itself 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. The
kScript (legacy) constructs carry over as AssemblyScript's: `if`/`else`,
`for`, `while`, `do`/`while`, `break`, and `continue`.

| Construct | Use it for |
| --- | --- |
| `if` / `else` | decision logic; the condition must be a `bool` |
| `for` | a known iteration count: a ring buffer, a cell block, a bounded scan |
| `while` | a condition-driven search with a bound you can prove |
| `break`, `continue` | leaving a loop early, skipping an iteration |

## if / else

`if (condition) { ... } else { ... }`. The condition is a boolean
expression; a number is not one, so `if (value)` is refused and `if
(value > 0.0)` or `if (!isNaN(value))` is the form. `else if` chains as
usual.

```typescript
let direction = 0.0;
if (close > prevClose) {
  direction = 1.0;
} else if (close < prevClose) {
  direction = -1.0;
}
```

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)).

## for

`for (let i = 0; i < end; i++) { ... }`. The counter is an `i32`; `i++`
and `i += 1` both work. Loop bodies work with locals and with the module's
buffers; a `StaticArray<f64>` sized from a param's `max` in `init()` or at
module start is the usual target, and `unchecked(ring[i])` skips the
bounds check once the index is provably inside.

```typescript
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
([Volume profile](volume-profile.md)).

## 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;
}
```

## 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.

## Every construct 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` that finds the first bar back whose range exceeds
the current one. The three snippets above appear verbatim inside
`state()`.

```typescript
import { input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_close, in_high, in_low, in_open } from "./gen/inputs";
import { emitRow, out_bars_above, out_bars_since_above, out_close_sum, out_direction, out_wider_back } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Sma } from "./sdk/ta";

param("period", 20, { min: 2, max: 200, description: "Average window and search depth" });
input("close", ohlcv.close);
input("open", ohlcv.open);
input("high", ohlcv.high);
input("low", ohlcv.low);
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;
let directionValue: f64 = 0.0;
let closeSum: f64 = NaN;
let barsSinceAbove: f64 = NaN;
let barsAbove: f64 = 0.0;
let widerBack: f64 = NaN;

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

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

export function state(): i32 {
  const close = in_close();
  const open = in_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] = in_high() - in_low()));
  unchecked((above[cursor] = !isNaN(average) && close > average));
  cursor = (cursor + 1) % size;
  if (count < size) count += 1;
  directionValue = direction;
  if (isNaN(average)) return 0;

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

  // 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;
  }
  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;
  }
  barsAbove = f64(ups);

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

export function finalize(): void {
  out_direction(directionValue);
  out_close_sum(closeSum);
  out_bars_since_above(barsSinceAbove);
  out_bars_above(barsAbove);
  out_wider_back(widerBack);
  emitRow();
}

export function reset(): void {
  sma.reset();
  cursor = 0;
  count = 0;
  prevClose = NaN;
  directionValue = 0.0;
  closeSum = NaN;
  barsSinceAbove = NaN;
  barsAbove = 0.0;
  widerBack = NaN;
}
```

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.

## Tips

- **Infinite loop protection.** Make sure a `while` condition can become
  false; the host stops a module that overruns its execution timeout and
  discards the worker, so a stuck loop fails the run loudly rather than
  hanging the chart.
- **Variable scope.** A `let` inside a loop or a branch is local to that
  block and gone when `state()` returns; module-level variables are the
  ones that persist across bars, and `reset()` must restore them.
- **Performance.** Loops run once per bar over the loaded history and
  again on every tick of the forming bar. 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.
- **No history array.** kScript forbade a `timeseries` inside a loop; an
  Indicator has no timeseries at all. The ring buffer is the window, and
  it is yours to size and index.
