---
title: "Control flow"
description: "Control flow decides what runs on each bar. An Indicator is AssemblyScript, so the toolkit is the familiar typed C-style one: if/else branches, for loops, the…"
order: 31
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. An Indicator is AssemblyScript,
so the toolkit is the familiar typed C-style one: `if`/`else` branches,
`for` loops, the `? :` ternary, and `switch`. Everything here runs inside
`state()` (or `finalize()`), which the host calls once per bar as it walks
the loaded history; that is the whole per-bar execution model, and it is
why a kScript (legacy) script and an Indicator read almost the same in
their bodies while differing in where the state lives.

## if / else

Branch on any boolean condition; the `else` is optional. Conditions are
real `bool` values: `if (value)` on a number is a compile error
(`AS200`), so compare (`if (close > open)`, `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
}
```

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

## for loops

Use a C-style `for` when you need a fixed number of iterations: summing a
window, scanning buckets, accumulating a value. The loop index is an
`i32`; cast it when it meets an `f64`.

```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]);
}
```

There is no history array to loop over (`close[n]` does not exist): a
window is a `StaticArray<f64>` ring the module fills one bar at a time,
sized once from the param's `max` ([Collections](../core-concepts/collections.md)).

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

## Everything together

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 four snippets above appear verbatim inside
`state()`.

```typescript
import { input, line, lower, none, ohlcv, output, param } from "./sdk/declare";
import { in_close, in_high, in_low, in_open } from "./gen/inputs";
import { emitRow, out_anchor, out_direction, out_folded, out_picked, out_signal, out_total } from "./gen/outputs";
import { p_mode, p_window } from "./gen/params";
import { Cross, Sma } from "./sdk/ta";

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" });
input("close", ohlcv.close);
input("open", ohlcv.open);
input("high", ohlcv.high);
input("low", ohlcv.low);
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();
let direction: f64 = 0.0;
let anchorValue: f64 = NaN;
let signalValue: f64 = NaN;
let totalValue: f64 = 0.0;
let picked: f64 = NaN;

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

export function state(): i32 {
  const close = in_close();
  const open = in_open();
  const high = in_high();
  const low = in_low();
  unchecked((ring[cursor] = close));
  cursor = (cursor + 1) % n;
  if (count < n) count += 1;
  const crossed = cross.update(close, sma.update(close));

  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;
  switch (mode) {
    case 0:
      picked = close;
      break;
    case 1:
      picked = high;
      break;
    default:
      picked = low;
  }

  anchorValue = anchor;
  signalValue = signal;
  totalValue = total;
  barIndex += 1;
  return 1;
}

export function finalize(): void {
  out_direction(direction);
  out_anchor(anchorValue);
  out_signal(signalValue);
  out_total(totalValue);
  out_picked(picked);
  out_folded(anchorValue + picked / 100.0 + totalValue);
  emitRow();
}

export function reset(): void {
  cursor = 0;
  count = 0;
  barIndex = 0;
  sma.reset();
  cross.reset();
  direction = 0.0;
  anchorValue = NaN;
  signalValue = NaN;
  totalValue = 0.0;
  picked = NaN;
}
```

## Loop limits

kScript capped a script at one million loop iterations. An Indicator has
no iteration counter: the host bounds the WHOLE evaluation with an
execution timeout, and a module that overruns it is stopped and its
worker discarded, so a runaway loop fails the run rather than hanging the
chart. In practice a loop bounded by a param's declared `max` never
approaches either limit; keep loop bounds tied to declared ranges and
the module stays cheap on a long history
([Execution model](../core-concepts/execution-model.md)).

## What to keep in mind

- **Per-bar execution.** `state()` re-runs on every bar. To carry a value
  across bars, keep it in a module-level `let` (the port of `persist`);
  a local declared inside `state()` is gone when the call returns, the
  port of a plain `var`.
- **`while` exists but use it sparingly.** A counted `for` is clearer and
  safer when you know the bound. Reach for `while` only for genuine
  search-until-found logic ([Loops](loops.md)), and make sure the
  condition can end.
- **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`.
- **`reset()` restores what the branches touched.** The host replays the
  forming bar through `reset()`; a module-level variable a branch set on
  a previous tick and `reset()` forgot is a stale read.
