Lua in Neovim: Break and Nested Loops — Star Patterns & Finding Divisible Numbers | Episode 11
Video: Lua in Neovim: Break and Nested Loops — Star Patterns & Finding Divisible Numbers | Episode 11 by Taught by Celeste AI - AI Coding Coach
Lua Break and Nested Loops: Star Patterns and Early Exits
Loops inside loops for grids and triangles.
breakto exit early when you've found what you're looking for. Build star rectangles, triangles, and a divisible-number search.
Today we put two loops side by side and exit early when we hit a target. The two new ideas: nesting (a loop inside a loop) and break (escape from a loop).
A star rectangle
for row = 1, 4 do
local line = ""
for col = 1, 8 do
line = line .. "* "
end
print(line)
end
Output:
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
Two nested for loops. Each iteration of the outer loop:
- Initialise
lineto empty string. - Inner loop appends
"* "eight times. - Print the row.
Four iterations of the outer loop = four rows. Eight iterations of the inner loop per row = eight stars.
A star triangle
for row = 1, 5 do
local line = ""
for col = 1, row do
line = line .. "* "
end
print(line)
end
Output:
*
* *
* * *
* * * *
* * * * *
The trick: the inner loop's bound is row, not a constant. Row 1 → 1 star, row 2 → 2 stars, and so on.
Same pattern works for a flipped triangle:
for row = 1, 5 do
local line = ""
for col = 1, 6 - row do
line = line .. "* "
end
print(line)
end
Inner bound is 6 - row instead of row. Row 1 → 5 stars, row 5 → 1 star.
break: stop searching when you find
for i = 1, 200 do
if i % 7 == 0 and i % 13 == 0 then
print("Found: " .. i)
break
end
end
Output:
Found: 91
We're looking for the first number divisible by both 7 and 13. Without break, the loop would continue past 91 to 182, then quit — wasted work. With break, we stop the moment we have an answer.
break exits the innermost enclosing loop, full stop. There's no "labeled break" in Lua — to exit several levels at once, use a flag:
local found = nil
for i = 1, 100 do
for j = 1, 100 do
if i * j == 42 then
found = {i, j}
break -- exits the inner loop only
end
end
if found then break end -- now exits the outer loop
end
Nested loops and time complexity
A nested loop where each runs N times performs N × N (= N²) operations:
for i = 1, 100 do
for j = 1, 100 do
-- 100 × 100 = 10,000 iterations total
end
end
For small N this is fine. For large N (say, 10,000), nested loops mean 100 million iterations — start watching the clock.
When you need to do something to every pair of items, nested loops are unavoidable. When you can avoid them — say, by sorting first or using a hash table — do.
A grid printer
for row = 1, 3 do
for col = 1, 3 do
io.write("[" .. row .. "," .. col .. "] ")
end
io.write("\n")
end
Output:
[1,1] [1,2] [1,3]
[2,1] [2,2] [2,3]
[3,1] [3,2] [3,3]
io.write is like print but doesn't add separators or newlines. Useful when building a single line piecewise.
Common nested-loop patterns
Multiplication table:
for i = 1, 10 do
for j = 1, 10 do
io.write(string.format("%4d ", i * j))
end
io.write("\n")
end
%4d pads each number to 4 characters wide for clean column alignment.
Search a 2D matrix:
local matrix = { {1,2,3}, {4,5,6}, {7,8,9} }
for r = 1, #matrix do
for c = 1, #matrix[r] do
if matrix[r][c] == 5 then
print("Found at " .. r .. "," .. c)
break -- exits inner; outer continues
end
end
end
Tables get covered properly in episode 17. For now: matrix[r][c] reads the cell at row r, column c.
Pair-wise comparisons (no duplicates):
local items = {1, 2, 3, 4}
for i = 1, #items - 1 do
for j = i + 1, #items do
print(items[i] .. " <-> " .. items[j])
end
end
j = i + 1 skips already-seen pairs — (1,2) is printed but (2,1) isn't.
When to use break vs restructuring
Sometimes break makes code clearer; sometimes a different loop shape is better.
break is fine when:
- You're searching and want to stop on first match.
- The loop has multiple exit points.
Restructure when:
- The condition is "loop while X" — use while X do.
- You're using flags to fake break — sometimes a function with return is cleaner.
function find_first_divisor(n)
for i = 2, n do
if n % i == 0 then return i end
end
return nil
end
return works as "break out of all the loops and the function" in one go. Often the cleanest pattern.
Common stumbles
Forgetting to reset the inner accumulator. local line = "" must be inside the outer loop, otherwise rows accumulate and you end up printing multi-line junk.
Using break to exit nested loops. It only exits the innermost. Use a flag or wrap in a function with return.
Off-by-one in the inner range. for col = 1, row and for col = 1, row - 1 look almost identical but produce subtly different patterns. Trace by hand for a small case.
Making N×N loops when N×log(N) would do. For sorting and lookups, hash tables (covered in episode 19) avoid the quadratic cost.
break outside a loop. break only exits loops — using it at the top level is a syntax error.
What's next
Episode 12: functions. Define and call your own. Parameters, return values, and a couple of utility functions.
Recap
Nested for loops for grids, triangles, multiplication tables. break exits the innermost loop. To exit multiple levels, use a flag or wrap in a function with return. Inner loop's bound can depend on the outer loop's variable for triangle/diagonal patterns. Keep an eye on the N² cost when scaling up.
Next episode: functions.