Skip to content

Loops: loop and br_if

A loop in WebAssembly does not iterate automatically. It is simply a labeled construct whose beginning is a branch target. You create iteration by placing a br_if at the bottom of the body that branches back to the loop label when the condition is still true.

(loop $label ...) marks the beginning of a loop. A br $label inside the body jumps back to the start — the opposite direction from br in a block. To exit the loop you either let execution reach the closing end (fall-through) or branch to an outer label.

(module
(func (export "countDown") (param $n i32)
(loop $again
;; body here
local.get $n
i32.const 1
i32.sub
local.tee $n ;; decrement $n and keep value on stack
i32.const 0
i32.gt_s
br_if $again))) ;; repeat while $n > 0
flowchart LR
  A["init locals"] --> B["loop $again (start)"]
  B --> C["body: update acc, increment i"]
  C --> D["i <= n?"]
  D -->|yes| B
  D -->|no| E["exit: return acc"]
Counted loop flow: br_if creates the back-edge

The function below sums the integers from 1 to n using a loop. Local $i is the counter; local $acc accumulates the total.

(module
(func (export "sumTo") (param $n i32) (result i32)
(local $i i32)
(local $acc i32)
i32.const 1
local.set $i ;; i = 1
i32.const 0
local.set $acc ;; acc = 0
(loop $again
local.get $i
local.get $acc
i32.add
local.set $acc ;; acc += i
local.get $i
i32.const 1
i32.add
local.set $i ;; i++
local.get $i
local.get $n
i32.le_s
br_if $again) ;; repeat while i <= n
local.get $acc)) ;; return acc
WebAssembly
OptionBenefitCost
loop + br_if (structured back-edge)Validatable at compile time and safer than an arbitrary backward gotoYou have to write the exit condition yourself every time — there’s no built-in for/while for the engine to manage
No arbitrary jumps between loopsThe loop’s boundaries are unambiguous to the engine, which simplifies compiling/optimizing itA complex CFG from native code has to go through a relooper-style pass before it can map onto loop/br
  • Assuming br_if inside a loop behaves like break — it actually jumps back to the start of the loop (closer to continue), not an exit from the loop
  • Forgetting a correct exit condition, so br_if $again is always true and the loop never ends (Wasm has no automatic infinite-loop guard)
  • An off-by-one in the exit condition — e.g. using i <= n where i < n was intended — causing the loop to run one iteration too few or too many, as in the sumTo example

💡 Real-world example

Because a Wasm loop’s back-edge is structured and explicit, an engine’s JIT can identify loop boundaries directly at compile time — without first having to reconstruct them from an arbitrary jump graph, the way a native-code JIT sometimes must.

In WAT, what does `br $l` do when `$l` is a `loop` label?
How do you EXIT a WAT loop?
What is the initial value of a local variable declared with `(local $x i32)`?