Skip to main content

Iteration

Learning Objectives

By the end of this lesson, you will be able to:

  • Write numeric for loops with custom start, end, and step values
  • Use while and repeat until loops appropriately
  • Control loop execution with break and continue
  • Iterate over arrays with ipairs preserving order
  • Iterate over dictionaries with pairs
  • Write nested loops for multi-dimensional data
  • Implement per-frame iteration in advance() for animations

AE/JS Syntax Comparison

Here's how iteration differs between languages:

ConceptJavaScriptLuau
Basic for loopfor (let i = 0; i < 5; i++)for i = 1, 5 do
Loop with stepfor (let i = 0; i <= 10; i += 2)for i = 0, 10, 2 do
Countdownfor (let i = 5; i >= 1; i--)for i = 5, 1, -1 do
While loopwhile (x > 0) { }while x > 0 do ... end
Do-whiledo { } while (x > 0)repeat ... until x <= 0
Array iterationarr.forEach((v, i) => {})for i, v in ipairs(arr) do
Object iterationfor (const k in obj) {}for k, v in pairs(obj) do
Breakbreak;break
Continuecontinue;continue
Critical Differences from JavaScript
  • 1-based indexing: Luau arrays start at index 1, not 0
  • Inclusive ranges: for i = 1, 5 do includes both 1 AND 5 (runs 5 times)
  • End keyword: Every loop ends with end
  • Countdown needs step: for i = 5, 1 do won't run - need for i = 5, 1, -1 do
  • No C-style for: Can't write for (init; condition; update) syntax
  • Condition inversion: repeat until uses "until condition is TRUE" (opposite of while)

Rive Context

Iteration is essential in Rive animations for processing collections of objects. You'll iterate over:

  • Arrays of enemies/particles to update positions each frame
  • Waypoints to calculate paths
  • Animation keyframes to interpolate values
  • Input events to process user interactions
  • Child nodes to apply transformations

Loops run inside lifecycle functions (init, advance, draw). For per-frame iteration over persistent collections, store them on self and iterate in advance.


Numeric For Loop

The most common loop for counting or stepping through ranges.

Basic Syntax

-- Basic: count from start to end (INCLUSIVE!)
for i = 1, 5 do
print(i) -- 1, 2, 3, 4, 5
end

JavaScript Equivalent:

// JavaScript (exclusive end, 0-based)
for (let i = 1; i <= 5; i++) {
console.log(i); // 1, 2, 3, 4, 5
}

With Step Value

-- Count by 2s
for i = 0, 10, 2 do
print(i) -- 0, 2, 4, 6, 8, 10
end

-- Countdown (MUST specify negative step!)
for i = 5, 1, -1 do
print(i) -- 5, 4, 3, 2, 1
end

JavaScript Equivalent:

// Step by 2
for (let i = 0; i <= 10; i += 2) {
console.log(i);
}

// Countdown
for (let i = 5; i >= 1; i--) {
console.log(i);
}

Common Gotcha: Missing Step for Countdown

Countdown Loops Need Negative Step!
-- This loop does NOTHING!
for i = 5, 1 do
print(i) -- Never runs! Default step is +1
end

-- This is correct:
for i = 5, 1, -1 do
print(i) -- 5, 4, 3, 2, 1
end

Loop Variable Scope

The loop variable is local to the loop body. You can assign to it, but it won't affect iteration:

for i = 1, 5 do
print(i) -- Still prints 1, 2, 3, 4, 5
i = i + 10 -- Assignment works, but doesn't change next iteration
end

-- If you need to skip values, use continue instead:
for i = 1, 10 do
if i % 2 == 0 then continue end -- Skip even numbers
print(i) -- 1, 3, 5, 7, 9
end

While Loop

Repeats while a condition is true. The condition is checked before each iteration.

local health = 100
while health > 0 do
health -= 10
print(`Health: {health}`)
end
-- Runs 10 times, prints 90, 80, 70... 0

JavaScript Equivalent:

let health = 100;
while (health > 0) {
health -= 10;
console.log(`Health: ${health}`);
}
Infinite Loop Danger

If the condition never becomes false, the loop runs forever and freezes your animation. Always ensure loop conditions can eventually become false.

-- DANGER: Infinite loop!
local x = 1
while x > 0 do
x += 1 -- x only gets bigger, never <= 0
end

-- SAFE: Has exit condition
local x = 100
while x > 0 do
x -= 1 -- Eventually reaches 0
end

Repeat Until

Like while, but the condition is checked after each iteration. The body always runs at least once.

local attempts = 0
repeat
attempts += 1
print(`Attempt {attempts}`)
until attempts >= 3
-- Always runs at least once, prints 1, 2, 3

JavaScript Equivalent:

// JavaScript do-while
let attempts = 0;
do {
attempts++;
console.log(`Attempt ${attempts}`);
} while (attempts < 3); // Note: condition is INVERTED
Condition is INVERTED from JavaScript!

JavaScript do-while continues WHILE condition is true. Luau repeat until stops WHEN condition becomes true.

// JavaScript: continues while attempts < 3
do { } while (attempts < 3);

// Luau: stops when attempts >= 3 (same logic, opposite syntax)
repeat until attempts >= 3

While vs Repeat Until

whilerepeat until
Checks condition firstChecks condition after
May not run at allAlways runs at least once
while condition dorepeat ... until condition
Stops when falseStops when true

Break and Continue

Control loop execution flow:

Break: Exit Loop Immediately

-- Search for first match, then stop
for i = 1, 100 do
if i > 10 then
print("Found it, stopping!")
break -- Exit loop immediately
end
print(i) -- Only prints 1-10
end
print("Loop finished")

Continue: Skip to Next Iteration

-- Skip even numbers
for i = 1, 10 do
if i % 2 == 0 then continue end -- Skip this iteration
print(i) -- Only odd numbers: 1, 3, 5, 7, 9
end

JavaScript Equivalent:

// Break
for (let i = 1; i <= 100; i++) {
if (i > 10) break;
console.log(i);
}

// Continue
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) continue;
console.log(i);
}

Iterating Over Arrays (ipairs)

Use ipairs for sequential arrays where order matters:

local enemies = {"Slime", "Goblin", "Mage", "Boss"}

for index, enemy in ipairs(enemies) do
print(`{index}: {enemy}`)
end
-- 1: Slime
-- 2: Goblin
-- 3: Mage
-- 4: Boss

JavaScript Equivalent:

const enemies = ["Slime", "Goblin", "Mage", "Boss"];

enemies.forEach((enemy, index) => {
console.log(`${index}: ${enemy}`); // Note: 0-based in JS!
});

// Or with for...of (no index)
for (const enemy of enemies) {
console.log(enemy);
}

When to Use ipairs

  • You need the numeric index
  • Order must be preserved (1, 2, 3...)
  • Array has no gaps (nil values)
  • Processing sequential collections
ipairs Stops at First nil
local arr = {1, 2, nil, 4, 5}

for i, v in ipairs(arr) do
print(v) -- Only prints 1, 2 (stops at nil!)
end

Iterating Over Dictionaries (pairs)

For key-value tables, use pairs():

local stats = {
health = 100,
mana = 50,
speed = 12
}

-- Dictionary iteration with pairs
for key, value in pairs(stats) do
print(`{key}: {value}`)
end
-- Order is NOT guaranteed!

JavaScript Equivalent:

const stats = {
health: 100,
mana: 50,
speed: 12
};

// for...in (keys only)
for (const key in stats) {
console.log(`${key}: ${stats[key]}`);
}

// Object.entries (key-value pairs)
for (const [key, value] of Object.entries(stats)) {
console.log(`${key}: ${value}`);
}

Why pairs?

pairs() is the standard, explicit way to iterate dictionaries in Luau. It makes it clear that the table is unordered and that you’re iterating key/value pairs.

Dictionary Order is NOT Guaranteed
local data = {a = 1, b = 2, c = 3}
for k, v in pairs(data) do
print(k) -- Could be a, b, c or c, a, b or any order!
end

Nested Loops

Loops can be nested for multi-dimensional operations:

-- Create a 3x3 grid
for row = 1, 3 do
for col = 1, 3 do
print(`({row}, {col})`)
end
end

JavaScript Equivalent:

for (let row = 1; row <= 3; row++) {
for (let col = 1; col <= 3; col++) {
console.log(`(${row}, ${col})`);
}
}
Breaking Out of Nested Loops

break only exits the innermost loop. To exit multiple levels, use a flag variable:

local found = false
for row = 1, 10 do
for col = 1, 10 do
if grid[row][col] == target then
found = true
break -- Only exits inner loop!
end
end
if found then break end -- Exit outer loop too
end

Exercises

Exercise 1: Range Loop ⭐

Premise

Iteration lets you repeat logic without copy-paste. Even simple sums are a fast way to practice the pattern.

Goal

By the end of this exercise, you will use a numeric for loop to sum a range.

Use Case

You need to total a range of values for a UI summary or debug readout. This same pattern is used for tick counters and progress math.

Example scenarios:

  • Summing data points
  • Generating tick values

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise1_RangeLoop
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise1 = {}

function init(self: Exercise1): boolean
local sum = 0
-- TODO: Sum numbers 1 through 5

print(`ANSWER: {sum}`)
return true
end

function draw(self: Exercise1, renderer: Renderer)
end

return function(): Node<Exercise1>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Use a for loop from 1 to 5
  2. Add each number to sum

Expected Output

Your console output should display the sum of all numbers from 1 through 5 (inclusive). This demonstrates the basic numeric for loop pattern.


Verify Your Answer

Verify Your Answer

Checklist

  • Loop runs 5 times
  • Sum equals 15
  • Output matches the expected line

Exercise 2: Step Loop ⭐

Premise

Step values let you skip through ranges. This is useful for even numbers, grid spacing, or sample steps.

Goal

By the end of this exercise, you will use a step value to build a sum.

Use Case

You are placing markers every 2 units. The sum of those markers gives a simple validation that your step loop works.

Example scenarios:

  • Even index sampling
  • Grid spacing

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise2_StepLoop
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise2 = {}

function init(self: Exercise2): boolean
local sum = 0
-- TODO: Loop from 2 to 10 in steps of 2 and add to sum

print(`ANSWER: {sum}`)
return true
end

function draw(self: Exercise2, renderer: Renderer)
end

return function(): Node<Exercise2>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Use for i = 2, 10, 2 do
  2. Add each i to sum

Expected Output

Your console output should display the sum of all even numbers from 2 to 10 (inclusive), demonstrating the step value in a for loop.


Verify Your Answer

Verify Your Answer

Checklist

  • Loop uses a step value of 2
  • Sum equals 30
  • Output matches the expected line

Exercise 3: ipairs Total ⭐

Premise

ipairs is the standard way to iterate arrays in order. It is what you use for lists of points, particles, or items.

Goal

By the end of this exercise, you will sum values with ipairs.

Use Case

You have a list of scores and need their total for a leaderboard summary.

Example scenarios:

  • Summing points
  • Iterating UI elements

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise3_IpairsTotal
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise3 = {}

function init(self: Exercise3): boolean
local scores = {5, 10, 15}
local total = 0

-- TODO: Use ipairs to sum the scores

print(`ANSWER: {total}`)
return true
end

function draw(self: Exercise3, renderer: Renderer)
end

return function(): Node<Exercise3>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Use ipairs(scores)
  2. Add each value to total

Expected Output

Your console output should display the total of all scores in the array, demonstrating iteration with ipairs.


Verify Your Answer

Verify Your Answer

Checklist

  • ipairs is used
  • Total equals 30
  • Output matches the expected line

Exercise 4: pairs Count ⭐

Premise

pairs iterates key-value tables. You will use this for dictionaries or config tables.

Goal

By the end of this exercise, you will count entries in a dictionary.

Use Case

You are iterating a settings table and need to count how many options are enabled.

Example scenarios:

  • Counting feature flags
  • Iterating config dictionaries

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise4_PairsCount
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise4 = {}

function init(self: Exercise4): boolean
local flags = {
glow = true,
shadow = false,
outline = true,
}

local enabled = 0
-- TODO: Use pairs to count true values

print(`ANSWER: {enabled}`)
return true
end

function draw(self: Exercise4, renderer: Renderer)
end

return function(): Node<Exercise4>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Iterate with pairs
  2. Increment enabled when value is true

Expected Output

Your console output should display the count of flags that have a value of true, demonstrating dictionary iteration with pairs.


Verify Your Answer

Verify Your Answer

Checklist

  • pairs is used
  • Enabled count equals 2
  • Output matches the expected line

Exercise 5: Nested Loop Grid ⭐⭐

Premise

Nested loops are how you build grids and repeated patterns. This is common for procedural visuals.

Goal

By the end of this exercise, you will use nested loops to count grid cells.

Use Case

You are building a 3x4 grid of markers. The count validates your loop structure before you start drawing.

Example scenarios:

  • Grid layouts
  • Procedural patterns

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise5_GridCount
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise5 = {}

function init(self: Exercise5): boolean
local count = 0
-- TODO: Use nested loops for rows=1..3 and cols=1..4
-- Increment count for each cell

print(`ANSWER: {count}`)
return true
end

function draw(self: Exercise5, renderer: Renderer)
end

return function(): Node<Exercise5>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Create an outer loop for rows (1 to 3)
  2. Create an inner loop for columns (1 to 4)
  3. Increment count for each cell

Expected Output

Your console output should display the total number of cells in the grid (rows × columns), demonstrating nested loop iteration.


Verify Your Answer

Verify Your Answer

Checklist

  • Two nested loops are used
  • Count equals 12
  • Output matches the expected line

Premise

break lets you exit early once you find what you need. This saves time and keeps logic simple.

Goal

By the end of this exercise, you will use break to stop a search loop.

Use Case

You are scanning a list of values to find the first value over a threshold. Once found, you stop.

Example scenarios:

  • Searching for a matching item
  • Early exit on a hit test

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise6_BreakSearch
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise6 = {}

function init(self: Exercise6): boolean
local values = {2, 4, 9, 3}
local found = 0

-- TODO: Find the first value > 5 and break

print(`ANSWER: {found}`)
return true
end

function draw(self: Exercise6, renderer: Renderer)
end

return function(): Node<Exercise6>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Loop through values in order
  2. When value > 5, store it in found and break

Expected Output

Your console output should display the first value in the array that exceeds the threshold. The loop should stop immediately after finding it.


Verify Your Answer

Verify Your Answer

Checklist

  • Break stops at the first match
  • Found value equals 9
  • Output matches the expected line

Exercise 7: Continue Filter ⭐⭐

Premise

continue lets you skip items you do not want to process. This is common when filtering input.

Goal

By the end of this exercise, you will skip negative values in a loop.

Use Case

You are averaging sensor readings and want to ignore invalid (negative) values.

Example scenarios:

  • Filtering invalid data
  • Skipping hidden UI items

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise7_ContinueFilter
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise7 = {}

function init(self: Exercise7): boolean
local values = {3, -1, 4, -2, 5}
local sum = 0
local count = 0

-- TODO: Skip negative values with continue

print(`ANSWER: sum={sum},count={count}`)
return true
end

function draw(self: Exercise7, renderer: Renderer)
end

return function(): Node<Exercise7>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Loop through values
  2. Use continue when value < 0
  3. Sum and count only positive values

Expected Output

Your console output should display:

  • sum= — the total of all positive (non-negative) values
  • count= — how many positive values were summed

Negative values should be skipped entirely.


Verify Your Answer

Verify Your Answer

Checklist

  • Negative values are skipped
  • Sum equals 12
  • Output matches the expected line

Exercise 8: String Iteration ⭐⭐

Premise

Sometimes you need to iterate characters, such as counting vowels or validating identifiers. This uses basic loops with string indexing.

Goal

By the end of this exercise, you will count vowels in a string.

Use Case

You are analyzing a label to determine if it meets naming rules. Counting vowels is a simple stand-in for character validation logic.

Example scenarios:

  • Validating text input
  • Running simple text analysis

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise8_StringIteration
  2. Attach and run:

    • Attach to any shape and press Play

Starter Code

--!strict

export type Exercise8 = {}

function init(self: Exercise8): boolean
local name = "Rive"
local vowels = 0

-- TODO: Loop through each character and count vowels (a,e,i,o,u)

print(`ANSWER: {vowels}`)
return true
end

function draw(self: Exercise8, renderer: Renderer)
end

return function(): Node<Exercise8>
return {
init = init,
draw = draw,
}
end

Assignment

Complete these tasks:

  1. Loop from 1 to #name
  2. Use name:sub(i, i) to read each char
  3. Count vowels a,e,i,o,u (case-insensitive not needed here)

Expected Output

Your console output should display the count of vowels (a, e, i, o, u) found in the string "Rive".


Verify Your Answer

Verify Your Answer

Checklist

  • Loop uses string length
  • Vowel count equals 2 (i and e)
  • Output matches the expected line

Knowledge Check

Q:What is the difference between `while` and `repeat until`?
Q:What does `break` do in a loop?
Q:When iterating over an array, what does `ipairs` provide?
Q:What is the output of: for i = 5, 1 do print(i) end
Q:When using `pairs(tbl)`, what does `for k, v in pairs(tbl) do` iterate over?
Q:How many times does this loop run: for i = 1, 5 do
Q:What happens if you use break inside nested loops?

Common Mistakes

Common Iteration Errors
  1. Missing step for countdown - for i = 10, 1 do won't run; need for i = 10, 1, -1 do
  2. Forgetting 1-based indexing - Arrays start at 1, not 0
  3. Expecting loop variable changes to affect iteration - Assigning to i works but doesn't change the loop's progression
  4. Infinite while loops - Ensure condition can eventually become false
  5. break in nested loops - Only exits innermost loop
  6. Assuming dictionary order - Key-value iteration order is NOT guaranteed
  7. Modifying array during iteration - Can cause unexpected behavior; iterate in reverse or collect indices first
  8. Using 0-based index - arr[0] is nil in Luau; use arr[1] for first element

Iteration Patterns Summary

PatternWhen to UseExample
for i = start, end doCounting, fixed iterationsfor i = 1, 10 do
for i = start, end, step doCustom increments, countdownsfor i = 10, 1, -1 do
while condition doUnknown iteration count, may not runwhile x > 0 do
repeat until conditionMust run at least oncerepeat until done
for i, v in ipairs(arr)Ordered array traversalfor i, enemy in ipairs(enemies)
for k, v in pairs(tbl)Dictionary traversalfor name, value in pairs(config)

Quick Reference

-- Numeric for loop (INCLUSIVE range!)
for i = 1, 10 do end -- 1 through 10
for i = 1, 10, 2 do end -- 1, 3, 5, 7, 9
for i = 10, 1, -1 do end -- 10 down to 1 (NEED -1!)

-- While and repeat
while condition do end
repeat until condition -- Runs at least once!

-- Array iteration (1-based!)
for index, value in ipairs(array) do end

-- Dictionary iteration (unordered!)
for key, value in pairs(tbl) do end

-- Loop control
break -- Exit loop immediately
continue -- Skip to next iteration

Next Steps