Why Use Loops?
Imagine you want to say a countdown from 10 to 1. Without loops you'd write ten separate llOwnerSay calls. With a loop, you write the action once and repeat it automatically.
Every loop has three parts:
- Initialisation
- Set up a counter or starting condition before the loop begins
- Condition
- Checked before (or after) each iteration. When FALSE, the loop stops.
- Body
- The code that runs each iteration
- Increment
- Update the counter so the condition eventually becomes FALSE
LSL has three loop types: while, for, and do-while. They all do the same thing in slightly different styles.
while Loop
The while loop checks its condition before each iteration. If the condition starts as FALSE, the body never runs.
default
{
touch_start(integer num_detected)
{
integer i = 5;
while (i > 0)
{
llOwnerSay((string)i + "...");
i--; // Decrement — without this, loop runs forever!
}
llOwnerSay("Liftoff!");
}
}
If your loop condition never becomes FALSE, the script runs forever and the simulator will eventually kill it. Always make sure your loop can end — check that the variable being tested actually changes inside the loop.
for Loop
The for loop bundles initialisation, condition, and increment into one tidy header. It's the go-to choice when you know exactly how many times you want to loop.
default
{
touch_start(integer num_detected)
{
integer total = 0;
integer i;
for (i = 1; i <= 10; i++)
{
total += i;
}
llOwnerSay("Sum of 1–10 = " + (string)total);
// Outputs: Sum of 1–10 = 55
}
}
The three parts of the for header are separated by semicolons:
i = 1— initialise counteri <= 10— keep going while this is truei++— increment after each iteration
do-while Loop
The do-while loop checks its condition after each iteration. This guarantees the body runs at least once, even if the condition is already FALSE.
default
{
state_entry()
{
integer attempts = 0;
do
{
attempts++;
llOwnerSay("Attempt " + (string)attempts);
}
while (attempts < 3);
llOwnerSay("Finished after " + (string)attempts + " attempt(s).");
}
}
When should you use do-while? Whenever you must perform an action at least once before checking whether to continue. A common example: validating user input where you need to collect it before you can check it.
No break — Using Flags Instead
Most languages have a break statement that exits a loop early. LSL does not. Instead, use a flag variable to control early exit:
default
{
touch_start(integer num_detected)
{
integer i = 0;
integer done = FALSE;
while (i < 100 && !done)
{
i++;
if (i == 7)
{
llOwnerSay("Found 7! Stopping.");
done = TRUE; // Flag causes the while condition to fail next check
}
}
llOwnerSay("Stopped at i = " + (string)i);
}
}
LSL does have a jump / @label mechanism for gotos, but flags are cleaner and easier to read. Save jump for very specific cases.
Loops and Performance
LSL scripts run on shared simulator hardware. A loop that takes too long can freeze your script and cause lag for everyone nearby. There are two rules:
- Keep loops short
- Hundreds of iterations are fine. Tens of thousands may cause a script-time error. If you need that many, split the work across multiple timer events.
- Avoid llSleep in loops
llSleep(n)pauses your script completely for n seconds, blocking all events. Use the timer event for delays instead.
// BAD — blocks all events for 50 seconds
// integer i;
// for (i = 0; i < 10; i++) { llOwnerSay(...); llSleep(5.0); }
// GOOD — uses the timer event
integer step = 0;
default
{
state_entry()
{
llSetTimerEvent(5.0);
}
timer()
{
step++;
llOwnerSay("Step " + (string)step);
if (step >= 10)
{
llSetTimerEvent(0);
llOwnerSay("Done.");
}
}
}
Practical: Multiplication Table
Here's a practical example using a nested for loop. It generates a simple 3×3 multiplication table and prints it to owner chat:
default
{
touch_start(integer num_detected)
{
integer row;
integer col;
string line;
for (row = 1; row <= 3; row++)
{
line = (string)row + " x: ";
for (col = 1; col <= 3; col++)
{
line = line + (string)(row * col) + " ";
}
llOwnerSay(line);
}
// Output:
// 1 x: 1 2 3
// 2 x: 2 4 6
// 3 x: 3 6 9
}
}
Nested loops are powerful — the inner loop completes fully for each single iteration of the outer loop. Just keep both loops small to stay within script time limits.