Iteration
Learning Objectives
By the end of this lesson, you will be able to:
- Write numeric
forloops with custom start, end, and step values - Use
whileandrepeat untilloops appropriately - Control loop execution with
breakandcontinue - Iterate over arrays with
ipairspreserving 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:
| Concept | JavaScript | Luau |
|---|---|---|
| Basic for loop | for (let i = 0; i < 5; i++) | for i = 1, 5 do |
| Loop with step | for (let i = 0; i <= 10; i += 2) | for i = 0, 10, 2 do |
| Countdown | for (let i = 5; i >= 1; i--) | for i = 5, 1, -1 do |
| While loop | while (x > 0) { } | while x > 0 do ... end |
| Do-while | do { } while (x > 0) | repeat ... until x <= 0 |
| Array iteration | arr.forEach((v, i) => {}) | for i, v in ipairs(arr) do |
| Object iteration | for (const k in obj) {} | for k, v in pairs(obj) do |
| Break | break; | break |
| Continue | continue; | continue |
- 1-based indexing: Luau arrays start at index 1, not 0
- Inclusive ranges:
for i = 1, 5 doincludes both 1 AND 5 (runs 5 times) - End keyword: Every loop ends with
end - Countdown needs step:
for i = 5, 1 dowon't run - needfor i = 5, 1, -1 do - No C-style for: Can't write
for (init; condition; update)syntax - Condition inversion:
repeat untiluses "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
-- 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}`);
}
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
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
while | repeat until |
|---|---|
| Checks condition first | Checks condition after |
| May not run at all | Always runs at least once |
while condition do | repeat ... until condition |
| Stops when false | Stops 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
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.
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})`);
}
}
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.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise1_RangeLoop
- Assets panel →
-
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:
- Use a for loop from 1 to 5
- 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
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.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise2_StepLoop
- Assets panel →
-
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:
- Use
for i = 2, 10, 2 do - 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
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.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise3_IpairsTotal
- Assets panel →
-
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:
- Use
ipairs(scores) - 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
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.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise4_PairsCount
- Assets panel →
-
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:
- Iterate with pairs
- 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
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.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise5_GridCount
- Assets panel →
-
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:
- Create an outer loop for rows (1 to 3)
- Create an inner loop for columns (1 to 4)
- 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
Checklist
- Two nested loops are used
- Count equals 12
- Output matches the expected line
Exercise 6: Break Search ⭐⭐
Premise
break lets you exit early once you find what you need. This saves time and keeps logic simple.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise6_BreakSearch
- Assets panel →
-
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:
- Loop through values in order
- 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
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.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise7_ContinueFilter
- Assets panel →
-
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:
- Loop through values
- Use continue when value < 0
- Sum and count only positive values
Expected Output
Your console output should display:
sum=— the total of all positive (non-negative) valuescount=— how many positive values were summed
Negative values should be skipped entirely.
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.
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:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise8_StringIteration
- Assets panel →
-
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:
- Loop from 1 to #name
- Use name:sub(i, i) to read each char
- 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
Checklist
- Loop uses string length
- Vowel count equals 2 (i and e)
- Output matches the expected line
Knowledge Check
Common Mistakes
- Missing step for countdown -
for i = 10, 1 dowon't run; needfor i = 10, 1, -1 do - Forgetting 1-based indexing - Arrays start at 1, not 0
- Expecting loop variable changes to affect iteration - Assigning to
iworks but doesn't change the loop's progression - Infinite while loops - Ensure condition can eventually become false
- break in nested loops - Only exits innermost loop
- Assuming dictionary order - Key-value iteration order is NOT guaranteed
- Modifying array during iteration - Can cause unexpected behavior; iterate in reverse or collect indices first
- Using 0-based index -
arr[0]is nil in Luau; usearr[1]for first element
Iteration Patterns Summary
| Pattern | When to Use | Example |
|---|---|---|
for i = start, end do | Counting, fixed iterations | for i = 1, 10 do |
for i = start, end, step do | Custom increments, countdowns | for i = 10, 1, -1 do |
while condition do | Unknown iteration count, may not run | while x > 0 do |
repeat until condition | Must run at least once | repeat until done |
for i, v in ipairs(arr) | Ordered array traversal | for i, enemy in ipairs(enemies) |
for k, v in pairs(tbl) | Dictionary traversal | for 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
- Continue to 2.1 Type Annotations & Inference
- Need a refresher? Review Quick Reference