Chapter 6: Control Flow #
"A program that can only march forward in a straight line is not very interesting. Branches and loops are what give programs their power."
So far, your programs have executed every statement in order, from top to bottom.
That changes now. In this chapter, you will learn how to make decisions with
if/else and how to repeat actions with while loops. Together, these two
constructs give your programs the ability to respond to conditions, process
collections, and solve complex problems.
By the end of this chapter, you will be able to:
- Use
if,else if, andelseto make decisions - Use
whileloops to repeat actions - Use
for-inloops for cleaner iteration over lists, sets, maps, strings, and ranges - Use all three forms of
range()for counted loops - Destructure tuples and lists directly in
for-inloop headers - Control loop execution with
breakandcontinue - Test membership with the
inandnot inoperators - Build collections concisely with list, map, and set comprehensions
- Handle errors with
try,catch, andthrow - Implement the classic FizzBuzz problem
- Apply common loop patterns: counting, accumulation, and search
- Nest control flow structures for complex logic
- Write practical programs that combine everything from Chapters 3 through 6
Control Flow Is GPS Navigation
Imagine you are driving with a GPS. The GPS does not simply say "drive
forward forever." It gives you turn-by-turn directions: "If you reach
Oak Street, turn left. Otherwise, continue straight. Repeat the next
three steps until you reach the highway." That is exactly what control
flow does for your programs. An if statement is a fork in the road -- go
left or go right depending on the sign. A while loop is a roundabout --
keep circling until you find your exit. A for-in loop is a tour bus
visiting every stop on a planned route. By the end of this chapter, you
will be the GPS, giving your programs precise directions for every
situation they might encounter.
6.1 Conditional Execution with if #
The if statement executes a block of code only when a condition is true:
{
let temperature = 35;
if (temperature > 30) {
emit "It's hot outside!";
}
}
The general form is:
if (<condition>) {
// executed when condition is true
}
The condition must be enclosed in parentheses. The body must be enclosed in curly braces, even if it contains only one statement.
How Conditions Are Evaluated #
The condition can be any expression. Neam evaluates it and determines whether the result is truthy or falsy:
- Falsy values:
false,nil - Truthy values: everything else (including
0,"", empty lists, etc.)
{
let score = 0;
if (score) {
emit "Score is truthy (even though it's zero)";
}
if (nil) {
emit "This will NOT print -- nil is falsy";
}
}
Expected output:
Score is truthy (even though it's zero)
6.2 if/else #
Often you want to do one thing when a condition is true and something different
when it is false. That is what else is for:
{
let age = 16;
if (age >= 18) {
emit "You can vote.";
} else {
emit "You cannot vote yet.";
}
}
Expected output:
You cannot vote yet.
The else block runs when the if condition is false. Exactly one of the two
blocks will execute -- never both, never neither.
6.3 Chaining with else if #
When you have more than two possibilities, chain them with else if:
{
let score = 85;
if (score >= 90) {
emit "Grade: A";
} else if (score >= 80) {
emit "Grade: B";
} else if (score >= 70) {
emit "Grade: C";
} else if (score >= 60) {
emit "Grade: D";
} else {
emit "Grade: F";
}
}
Expected output:
Grade: B
How it works:
- Neam evaluates the first condition (
score >= 90). - If true, it executes that block and skips all remaining
else if/elseblocks. - If false, it moves to the next condition (
score >= 80). - This continues until a condition is true or the
elseblock is reached. - Only one block ever executes. Once a match is found, the rest are skipped.
The order of conditions matters. Put more specific (higher threshold) conditions
first. If you accidentally put score >= 60 first, every score above 60 would
match it, and you would never reach the higher grades.
6.4 while Loops #
A while loop repeats a block of code as long as a condition remains true:
{
let i = 1;
while (i <= 5) {
emit "Count: " + str(i);
i = i + 1;
}
}
Expected output:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
The general form is:
while (<condition>) {
// body -- executed repeatedly
}
Execution flow:
- Evaluate the condition.
- If true, execute the body.
- Go back to step 1.
- If false, skip the body and continue with the next statement after the loop.
The Importance of the Update Step #
Every while loop needs three components:
- Initialization: Set up the loop variable before the loop (
let i = 1). - Condition: Test whether to continue (
i <= 5). - Update: Modify the loop variable inside the body (
i = i + 1).
If you forget the update step, the condition will never become false, and you will have an infinite loop:
// WARNING: Infinite loop -- do NOT run this
{
let i = 1;
while (i <= 5) {
emit str(i);
// MISSING: i = i + 1;
}
}
If you accidentally create an infinite loop, press Ctrl+C in your terminal
to stop the program.
6.5 for-in Loops #
While while loops are flexible, they require you to manually manage an index
variable. Neam provides for-in loops for cleaner iteration over collections:
{
let fruits = ["apple", "banana", "cherry", "date"];
for (fruit in fruits) {
emit "I like " + fruit;
}
}
Expected output:
I like apple
I like banana
I like cherry
I like date
The general form is:
for (<variable> in <iterable>) {
// body -- executed once per element
}
Compare this with the equivalent while loop:
// The while version -- more verbose
{
let fruits = ["apple", "banana", "cherry", "date"];
let i = 0;
while (i < len(fruits)) {
emit "I like " + fruits[i];
i = i + 1;
}
}
The for-in loop is shorter, clearer, and eliminates the entire class of
off-by-one errors that plague while loops.
What Can You Iterate Over? #
The for-in loop works with every iterable type in Neam. Here is a complete
overview:
List iteration (the most common case):
{
for (item in [1, 2, 3]) {
emit str(item);
}
}
Set iteration -- visits every unique element, but order is not guaranteed:
{
let tags = set("urgent", "bug", "backend");
for (tag in tags) {
emit f"Tag: {tag}";
}
}
String iteration -- visits each character:
{
for (ch in "Neam") {
emit ch;
}
}
Expected output:
N
e
a
m
TypedArray iteration -- works exactly like a list. The TypedArray() constructor
is the long form; float_array() and int_array() (introduced in Chapter 4) are
shorthand equivalents:
{
let buffer = TypedArray("float64", [1.1, 2.2, 3.3]);
// Equivalent to: let buffer = float_array([1.1, 2.2, 3.3]);
for (val in buffer) {
emit str(val);
}
}
for-in with Index via enumerate #
When you need both the element and its position, use .enumerate():
{
let colors = ["red", "green", "blue"];
for ((i, color) in colors.enumerate()) {
emit str(i) + ": " + color;
}
}
Expected output:
0: red
1: green
2: blue
The .enumerate() method wraps each element in an (index, value) tuple.
Destructure it with (i, item) in the loop header for clean access to both
the position and the value.
for-in with range #
To iterate over a sequence of numbers, use range(). It comes in three forms:
{
// range(n) -- counts from 0 to n-1
for (i in range(5)) {
emit f"Step {i}"; // 0, 1, 2, 3, 4
}
// range(start, end) -- counts from start to end-1
for (i in range(1, 6)) {
emit f"Count: {i}"; // 1, 2, 3, 4, 5
}
// range(start, end, step) -- with custom step
for (i in range(0, 20, 5)) {
emit f"By fives: {i}"; // 0, 5, 10, 15
}
}
Expected output:
Step 0
Step 1
Step 2
Step 3
Step 4
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
By fives: 0
By fives: 5
By fives: 10
By fives: 15
range(n) is the shortest way to repeat something n times. range(start, end)
is useful when you need a specific starting point. range(start, end, step) lets
you skip values -- perfect for counting by twos, fives, tens, or any interval.
Forgetting range() for Counted Loops
A frequent beginner mistake is trying to iterate over a number directly
instead of using range():
// WRONG -- this does NOT work as expected
// for (i in 5) { ... }
// CORRECT -- use range() for counted loops
// for (i in range(5)) { ... }
Remember: range() generates a sequence of numbers. A bare number is not
a collection you can iterate over. If you want to repeat something n times,
always wrap it in range().
Try It Yourself
Write a countdown from 10 to 1 using for-in with range(). Hint: you can
use range(10, 0, -1) to count backward. Then emit "Liftoff!" after the
loop finishes.
for-in with Maps #
Maps support several iteration styles. You can iterate over keys directly, over
values, or over key-value pairs using .entries():
Iterating over keys:
{
let config = {
"host": "localhost",
"port": 8080,
"debug": true
};
for (key in config.keys()) {
emit key + " = " + str(config[key]);
}
}
Iterating over key-value pairs with .entries():
This is the idiomatic way to access both keys and values. Each entry is a
(key, value) tuple that you destructure in the loop header:
{
let scores = {
"Alice": 95,
"Bob": 87,
"Carol": 92
};
for ((name, score) in scores.entries()) {
emit f"{name} scored {score}";
}
}
Expected output:
Alice scored 95
Bob scored 87
Carol scored 92
Iterating over values only:
{
let prices = {"apple": 1.20, "banana": 0.50, "cherry": 3.00};
let total = 0;
for (price in prices.values()) {
total = total + price;
}
emit f"Total: {total}"; // Total: 4.7
}
Destructuring in For Loops #
When iterating over lists of tuples or using .enumerate(), you can destructure
directly in the loop header. This is cleaner than accessing elements by index:
{
// Destructuring enumerate results
let colors = ["red", "green", "blue"];
for ((i, color) in colors.enumerate()) {
emit f"{i}: {color}";
}
// Destructuring tuple pairs from zip
let names = ["Alice", "Bob"];
let scores = [95, 87];
for ((name, score) in names.zip(scores)) {
emit f"{name} scored {score}";
}
}
Expected output:
0: red
1: green
2: blue
Alice scored 95
Bob scored 87
Destructuring with the Spread Operator #
Outside of loops, you can also destructure lists with the ...rest spread
syntax to capture remaining elements:
{
let data = [10, 20, 30, 40, 50];
let [first, second, ...rest] = data;
emit f"first = {first}"; // first = 10
emit f"second = {second}"; // second = 20
emit f"rest = {rest}"; // rest = [30, 40, 50]
}
The ...rest variable captures everything not matched by the preceding
variables. It must appear last in the pattern.
This pattern is useful for processing headers and tails of data:
{
let csv_rows = [
"Name,Score,Grade",
"Alice,95,A",
"Bob,87,B",
"Carol,92,A"
];
let [header, ...data_rows] = csv_rows;
emit f"Header: {header}";
emit f"Data rows: {len(data_rows)}";
for (row in data_rows) {
emit f" {row}";
}
}
Expected output:
Header: Name,Score,Grade
Data rows: 3
Alice,95,A
Bob,87,B
Carol,92,A
Destructuring makes your intent clear: instead of receiving a tuple and pulling it apart inside the loop, you name each piece right where you need it. This pattern becomes especially powerful when working with structured data in later chapters.
When to Use for-in vs. while #
| Situation | Use |
|---|---|
| Iterating over a list, set, map, or string | for-in |
| Iterating a known range of numbers | for-in with range() |
| Loop where you need early exit with complex conditions | while |
| Loop where the number of iterations is unknown | while |
| Infinite loop with a break condition | while (true) + break |
In practice, for-in covers most iteration needs. Reserve while for cases
where you need fine-grained control over the loop variable or complex exit
conditions.
6.6 break and continue #
Sometimes you need to alter the normal flow of a loop. Neam provides two keywords for this purpose:
breakimmediately exits the innermost enclosing loop.continueskips the rest of the current iteration and moves to the next one.
Both keywords work in while loops and for-in loops.
break: Exit Early #
{
let numbers = [4, 7, 2, 9, 1, 8, 3];
// Find the first number greater than 5
for (num in numbers) {
if (num > 5) {
emit "Found: " + str(num);
break; // Stop searching
}
}
emit "Search complete";
}
Expected output:
Found: 7
Search complete
Without break, the loop would continue through the entire list. With break,
it stops as soon as the condition is met.
continue: Skip an Iteration #
{
// Print only odd numbers from 1 to 10
for (i in range(1, 11)) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
emit str(i);
}
}
Expected output:
1
3
5
7
9
continue jumps back to the top of the loop, skipping any code below it for the
current iteration.
Practical Example: Validating a List #
A common pattern is to skip invalid items and process only valid ones:
{
let responses = ["APPROVED", "ERROR", "APPROVED", nil, "REJECTED", "APPROVED"];
let approved_count = 0;
for (response in responses) {
// Skip nil responses
if (response == nil) {
continue;
}
// Stop on first rejection
if (response == "REJECTED") {
emit "Process halted: rejection found";
break;
}
if (response == "APPROVED") {
approved_count = approved_count + 1;
}
}
emit "Approved before halt: " + str(approved_count);
}
Expected output:
Process halted: rejection found
Approved before halt: 2
break and continue in while Loops #
Both break and continue work identically in while loops:
{
// Using break to exit a while loop early
let i = 0;
while (true) {
if (i >= 5) {
break;
}
emit str(i);
i = i + 1;
}
// Output: 0 1 2 3 4
// Using continue to skip iterations in a while loop
let j = 0;
while (j < 10) {
j = j + 1;
if (j % 3 == 0) {
continue; // Skip multiples of 3
}
emit str(j);
}
// Output: 1 2 4 5 7 8 10
}
Nested Loops: break and continue Affect the Innermost Loop #
When you have nested loops, break and continue always apply to the
innermost enclosing loop -- never to an outer loop:
{
for (row in range(3)) {
for (col in range(3)) {
if (col == 1) {
continue; // Skips col=1 only, does NOT skip the row
}
emit f"({row}, {col})";
}
}
}
Expected output:
(0, 0)
(0, 2)
(1, 0)
(1, 2)
(2, 0)
(2, 2)
The continue skips only the inner loop's current iteration (where col == 1).
The outer loop over rows continues normally. If you need to break out of multiple
levels, use a flag variable:
{
let done = false;
for (row in range(5)) {
for (col in range(5)) {
if (row * col > 6) {
done = true;
break; // Breaks inner loop
}
emit f"({row}, {col})";
}
if (done) {
break; // Breaks outer loop
}
}
}
6.7 The in Operator for Membership Testing #
The in operator tests whether a value exists within a collection. It returns
true or false and works with lists, sets, maps, strings, and ranges:
in with Lists #
{
let primes = [2, 3, 5, 7, 11, 13];
if (7 in primes) {
emit "7 is prime";
}
if (4 in primes) {
emit "This will not print";
}
}
Expected output:
7 is prime
in with Maps #
When used with a map, in checks whether the value is a key in the map:
{
let config = {"host": "localhost", "port": 8080, "debug": true};
if ("host" in config) {
emit f"host = {config['host']}";
}
if ("timeout" in config) {
emit "This will not print";
}
}
Expected output:
host = localhost
in with Strings #
For strings, in performs a substring search:
{
let message = "Hello, World!";
if ("World" in message) {
emit "Found 'World' in the message";
}
if ("ell" in message) {
emit "Found 'ell' in the message";
}
}
Expected output:
Found 'World' in the message
Found 'ell' in the message
in with Sets and Ranges #
Sets provide O(1) membership testing, making in extremely fast even for
large collections:
{
let allowed = set("admin", "editor", "viewer");
if ("admin" in allowed) {
emit "admin is an allowed role";
}
// Ranges support in without generating all values
if (50 in range(100)) {
emit "50 is in range(100)";
}
}
not in: Negated Membership #
Use not in to check that a value is not present:
{
let forbidden = set("DROP", "DELETE", "TRUNCATE");
let command = "SELECT";
if (command not in forbidden) {
emit f"'{command}' is safe to execute";
}
}
Expected output:
'SELECT' is safe to execute
Practical Example: Input Validation #
The in operator is especially useful for validating inputs before processing:
{
let valid_commands = ["help", "status", "run", "stop", "restart"];
let user_input = "pause";
if (user_input in valid_commands) {
emit f"Executing: {user_input}";
} else {
emit f"Unknown command: '{user_input}'";
emit f"Valid commands: {valid_commands}";
}
}
Expected output:
Unknown command: 'pause'
Valid commands: ["help", "status", "run", "stop", "restart"]
The in operator and the .contains() method do the same thing, but in
reads more naturally in conditionals:
// Both are equivalent:
if (3 in [1, 2, 3]) { ... }
if ([1, 2, 3].contains(3)) { ... }
Prefer in for readability, especially in if conditions and guard clauses.
6.8 Comprehensions #
Comprehensions let you build new collections from existing ones in a single, concise expression. Instead of writing a loop that initializes an empty list, iterates, and conditionally appends, you express the same logic declaratively.
For-loop statements use parentheses: for (x in list) { ... }.
Comprehensions omit parentheses: [x * 2 for x in list]. This distinction
mirrors the difference between statements (which need a block) and expressions
(which produce a value inline).
List Comprehensions #
The most common form. Build a new list by applying an expression to each element of an iterable:
{
// Squares of 0 through 9
let squares = [x * x for x in range(10)];
emit str(squares);
// [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
}
The general form is:
[<expression> for <variable> in <iterable>]
This is equivalent to the loop:
{
let squares = [];
for (x in range(10)) {
squares.push(x * x);
}
}
Filtered Comprehensions #
Add an if clause to include only elements that satisfy a condition:
{
// Even numbers from 0 to 19
let evens = [x for x in range(20) if x % 2 == 0];
emit str(evens);
// [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
// Words longer than 3 characters
let words = ["hi", "hello", "cat", "world", "go"];
let long_words = [w for w in words if len(w) > 3];
emit str(long_words);
// ["hello", "world"]
}
The general form with a filter:
[<expression> for <variable> in <iterable> if <condition>]
Map Comprehensions #
Build a map (dictionary) from an iterable. Each iteration produces a key-value pair:
{
let words = ["hello", "world", "neam"];
// Map each word to its length
let word_lengths = {word: len(word) for word in words};
emit str(word_lengths);
// {"hello": 5, "world": 5, "neam": 4}
// Invert a map (swap keys and values)
let original = {"a": 1, "b": 2, "c": 3};
let inverted = {str(v): k for (k, v) in original.entries()};
emit str(inverted);
// {"1": "a", "2": "b", "3": "c"}
}
The general form:
{<key_expr>: <value_expr> for <variable> in <iterable>}
Set Comprehensions #
Build a set from an iterable, automatically removing duplicates:
{
let words = ["hello", "world", "neam", "hi", "go"];
// Unique word lengths
let lengths = set(len(w) for w in words);
emit str(lengths);
// {5, 4, 2} (order may vary)
}
Nested Comprehensions #
Use multiple for clauses to iterate over combinations:
{
// All (x, y) pairs where x and y are 0, 1, 2
let pairs = [(x, y) for x in range(3) for y in range(3)];
emit str(pairs);
// [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]
// Flatten a list of lists
let nested = [[1, 2], [3, 4], [5, 6]];
let flat = [item for sublist in nested for item in sublist];
emit str(flat);
// [1, 2, 3, 4, 5, 6]
}
The outer for runs first, and the inner for runs for each iteration of the
outer. This is equivalent to:
{
let pairs = [];
for (x in range(3)) {
for (y in range(3)) {
pairs.push((x, y));
}
}
}
Comprehensions with Destructuring #
Combine comprehensions with tuple destructuring for expressive data transformations:
{
let students = [("Alice", 95), ("Bob", 72), ("Carol", 88)];
// Names of students who scored above 80
let honor_roll = [name for (name, score) in students if score > 80];
emit str(honor_roll);
// ["Alice", "Carol"]
// Build a grade map
let grades = {name: (if score >= 90 { "A" } else if score >= 80 { "B" } else { "C" })
for (name, score) in students};
emit str(grades);
// {"Alice": "A", "Bob": "C", "Carol": "B"}
}
When to Use Comprehensions vs. Loops #
| Situation | Prefer |
|---|---|
| Simple transform or filter | Comprehension |
| Complex logic with multiple statements | for-in loop |
| Side effects (emit, mutation) in the body | for-in loop |
| Building a collection from iteration | Comprehension |
Need break or continue |
for-in loop |
Comprehensions are best for straightforward "take a collection, produce a new
collection" patterns. When the logic inside the loop is complex -- multiple
statements, error handling, side effects -- a regular for-in loop is clearer.
Empty Comprehensions
If the iterable is empty or the filter excludes everything, the result is simply an empty collection:
{
let empty = [x for x in [] if x > 0];
emit str(empty); // []
let none_match = [x for x in range(10) if x > 100];
emit str(none_match); // []
}
6.9 Error Handling with try/catch/throw #
Programs encounter errors: a file is missing, an API returns an unexpected response,
a division by zero occurs. Neam provides structured error handling with try,
catch, and throw.
Basic try/catch #
{
try {
let result = 10 / 0;
emit str(result);
} catch (error) {
emit "An error occurred: " + str(error);
}
emit "Program continues after the error";
}
The try block runs normally. If any statement inside it produces an error, execution
immediately jumps to the catch block. The error value is bound to the variable
name you specify (here, error). After the catch block finishes, the program
continues normally.
Throwing Errors #
You can throw your own errors with the throw keyword:
fun divide(a, b) {
if (b == 0) {
throw "Division by zero";
}
return a / b;
}
{
try {
emit str(divide(10, 3)); // 3.333...
emit str(divide(10, 0)); // This throws
emit "This line is never reached";
} catch (e) {
emit "Error: " + str(e); // Error: Division by zero
}
}
Error Handling Patterns #
Pattern 1: Graceful degradation
fun safe_parse_number(text) {
try {
return num(text);
} catch (e) {
return nil; // Return nil instead of crashing
}
}
{
let a = safe_parse_number("42");
let b = safe_parse_number("not a number");
emit str(a); // 42
emit str(b); // nil
}
Pattern 2: Retry logic
fun attempt_with_retry(values, max_attempts) {
let attempts = 0;
for (val in values) {
try {
if (val < 0) {
throw "Negative value: " + str(val);
}
emit "Processed: " + str(val);
return val;
} catch (e) {
attempts = attempts + 1;
emit "Attempt " + str(attempts) + " failed: " + str(e);
if (attempts >= max_attempts) {
throw "Max retries exceeded";
}
}
}
}
{
try {
attempt_with_retry([-1, -2, 5, 10], 3);
} catch (e) {
emit "Final error: " + str(e);
}
}
try/catch is introduced here at a basic level. Chapter 8 covers
error handling in depth, including the Result type pattern, error propagation
across function boundaries, and best practices for agent error handling.
6.10 FizzBuzz: A Complete Walkthrough #
FizzBuzz is the most famous programming interview problem. The rules are:
- For numbers 1 through n:
- If the number is divisible by both 3 and 5, print "FizzBuzz"
- If the number is divisible by 3 (but not 5), print "Fizz"
- If the number is divisible by 5 (but not 3), print "Buzz"
- Otherwise, print the number
Here is the Neam implementation from examples/04_control_flow.neam:
fun fizzbuzz(n) {
let i = 1;
while (i <= n) {
if (i % 15 == 0) {
emit str(i) + ": FizzBuzz";
} else if (i % 3 == 0) {
emit str(i) + ": Fizz";
} else if (i % 5 == 0) {
emit str(i) + ": Buzz";
} else {
emit str(i);
}
i = i + 1;
}
}
{
emit "=== FizzBuzz (1-20) ===";
fizzbuzz(20);
}
Modern version with for-in and f-strings:
Here is the same FizzBuzz rewritten using for-in with range() and f-strings,
which produces cleaner, more idiomatic Neam:
fun fizzbuzz_modern(n) {
for (i in range(1, n + 1)) {
if (i % 15 == 0) {
emit f"{i}: FizzBuzz";
} else if (i % 3 == 0) {
emit f"{i}: Fizz";
} else if (i % 5 == 0) {
emit f"{i}: Buzz";
} else {
emit str(i);
}
}
}
{
emit "=== FizzBuzz Modern (1-20) ===";
fizzbuzz_modern(20);
}
The modern version eliminates the manual loop variable (let i = 1 and
i = i + 1), which removes an entire class of potential bugs. Both versions
produce identical output -- choose whichever you find more readable. As you
gain experience, you will likely prefer the for-in style for its brevity.
Modify fizzbuzz_modern to use 4 and 7 instead of 3 and 5. What number
replaces 15 as the combined check? (Hint: think about least common
multiples.) Run it for 1 to 30 and compare the output to the original.
Step-by-Step Trace #
Let us trace the first several iterations:
| i | i % 15 | i % 3 | i % 5 | Branch Taken | Output |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1 | else | 1 |
| 2 | 2 | 2 | 2 | else | 2 |
| 3 | 3 | 0 | 3 | i % 3 == 0 | 3: Fizz |
| 4 | 4 | 1 | 4 | else | 4 |
| 5 | 5 | 2 | 0 | i % 5 == 0 | 5: Buzz |
| 6 | 6 | 0 | 1 | i % 3 == 0 | 6: Fizz |
| ... | |||||
| 15 | 0 | 0 | 0 | i % 15 == 0 | 15: FizzBuzz |
Why check i % 15 first? Because 15 is divisible by both 3 and 5. If we
checked i % 3 first, 15 would match the Fizz condition and we would never
reach FizzBuzz. The order of else if conditions matters.
Why i % 15 instead of (i % 3 == 0) && (i % 5 == 0)? Both work. Checking
i % 15 == 0 is a concise shorthand because any number divisible by both 3 and 5
is also divisible by 15 (their least common multiple).
Full Expected Output #
=== FizzBuzz (1-20) ===
1
2
3: Fizz
4
5: Buzz
6: Fizz
7
8
9: Fizz
10: Buzz
11
12: Fizz
13
14
15: FizzBuzz
16
17
18: Fizz
19
20: Buzz
6.11 Loop Patterns #
Most loops follow one of a few common patterns. Learning to recognize these patterns will make your code easier to write and understand.
Pattern 1: Counting #
Execute something a fixed number of times:
{
// Print a 5-line ruler
let i = 1;
while (i <= 5) {
emit str(i) + " |" + "====";
i = i + 1;
}
}
Pattern 2: Accumulation #
Build up a result across iterations:
{
// Sum the numbers 1 through 100
let sum = 0;
let i = 1;
while (i <= 100) {
sum = sum + i;
i = i + 1;
}
emit "Sum of 1 to 100: " + str(sum); // 5050
}
This is the famous formula verified by Gauss as a child. You can verify:
n * (n + 1) / 2 = 100 * 101 / 2 = 5050.
Pattern 3: Search #
Look for a value that meets a condition:
{
let numbers = [4, 7, 2, 9, 1, 8, 3];
let target = 9;
let found = false;
let index = -1;
let i = 0;
while (i < len(numbers)) {
if (numbers[i] == target) {
found = true;
index = i;
}
i = i + 1;
}
if (found) {
emit "Found " + str(target) + " at index " + str(index);
} else {
emit str(target) + " not found";
}
}
Expected output:
Found 9 at index 3
Pattern 4: Filtering #
Build a new collection containing only elements that match a condition:
fun get_even_numbers(numbers) {
let result = [];
let i = 0;
while (i < len(numbers)) {
if (numbers[i] % 2 == 0) {
result = result + [numbers[i]];
}
i = i + 1;
}
return result;
}
{
let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evens = get_even_numbers(data);
emit "Even numbers: " + str(evens);
}
Expected output:
Even numbers: [2, 4, 6, 8, 10]
Pattern 5: Transformation #
Apply an operation to each element and collect the results:
fun double_all(numbers) {
let result = [];
let i = 0;
while (i < len(numbers)) {
result = result + [numbers[i] * 2];
i = i + 1;
}
return result;
}
{
let data = [1, 2, 3, 4, 5];
let doubled = double_all(data);
emit "Doubled: " + str(doubled); // [2, 4, 6, 8, 10]
}
Pattern 6: Collecting with For-In #
The filtering pattern above used a while loop with manual indexing. Here is
the cleaner version using for-in, which is the idiomatic approach in modern
Neam:
{
// Pattern: Build a new list using for-in
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evens = [];
for (n in numbers) {
if (n % 2 == 0) {
evens.push(n);
}
}
emit f"Even numbers: {evens}";
}
Expected output:
Even numbers: [2, 4, 6, 8, 10]
This pattern -- initialize an empty list, iterate with for-in, conditionally
push elements -- is one of the most frequently used patterns in real programs.
You will see it again and again when processing data, filtering results from
agents, and building reports. Compare this to Pattern 4 above: the logic is
identical, but the for-in version is shorter and less error-prone because
there is no index variable to manage.
Using the collecting pattern, write a program that takes a list of words
and collects only those with more than 4 characters into a new list. Test
with: ["hi", "hello", "cat", "world", "Neam", "programming"].
6.12 Nested Control Flow #
You can place if statements inside while loops, while loops inside if
statements, and any combination thereof. This is called nesting.
Example: Multiplication Table #
{
emit "=== Multiplication Table (1-5) ===";
emit "";
let row = 1;
while (row <= 5) {
let line = "";
let col = 1;
while (col <= 5) {
let product = row * col;
let cell = str(product);
// Pad single-digit numbers with a space for alignment
if (product < 10) {
cell = " " + cell;
}
line = line + cell + " ";
col = col + 1;
}
emit line;
row = row + 1;
}
}
Expected output:
=== Multiplication Table (1-5) ===
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
Example: Finding Prime Numbers #
fun is_prime(n) {
if (n < 2) {
return false;
}
let i = 2;
while (i * i <= n) {
if (n % i == 0) {
return false;
}
i = i + 1;
}
return true;
}
{
emit "Prime numbers up to 50:";
let primes = [];
let n = 2;
while (n <= 50) {
if (is_prime(n)) {
primes = primes + [n];
}
n = n + 1;
}
emit str(primes);
}
Expected output:
Prime numbers up to 50:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
This program combines:
- A function (is_prime) with a while loop and early return
- The main block with a while loop and an if inside it
- List accumulation to collect results
6.13 Practical Patterns #
Let us build a few more practical programs that combine control flow with everything you have learned so far.
Pattern: Iterative Fibonacci (vs. Recursive) #
In Chapter 5, you saw a recursive Fibonacci function. Here is the iterative version, which is dramatically more efficient:
fun fibonacci_iterative(n) {
if (n <= 1) {
return n;
}
let prev = 0;
let curr = 1;
let i = 2;
while (i <= n) {
let next = prev + curr;
prev = curr;
curr = next;
i = i + 1;
}
return curr;
}
{
let i = 0;
while (i <= 15) {
emit "fib(" + str(i) + ") = " + str(fibonacci_iterative(i));
i = i + 1;
}
}
The recursive version has exponential time complexity (O(2^n)). This iterative
version runs in linear time (O(n)) -- a massive improvement. For fib(30), the
recursive version makes over a billion calls; the iterative version makes 29
loop iterations.
Pattern: Countdown Timer #
{
let count = 10;
emit "Countdown:";
while (count > 0) {
emit " " + str(count) + "...";
count = count - 1;
}
emit " Liftoff!";
}
Pattern: Running Total with Sentinel #
Process values until a sentinel (stop) value is reached:
{
let values = [10, 25, 30, 15, -1]; // -1 is the sentinel
let total = 0;
let i = 0;
while (i < len(values)) {
if (values[i] == -1) {
// Sentinel found -- stop processing
i = len(values); // Force loop exit
} else {
total = total + values[i];
i = i + 1;
}
}
emit "Total: " + str(total); // 80
}
Pattern: Number Guessing Game Simulation #
fun check_guess(secret, guess) {
if (guess == secret) {
return "correct";
}
if (guess < secret) {
return "too low";
}
return "too high";
}
{
let secret = 42;
let guesses = [20, 50, 35, 45, 42];
let i = 0;
let found = false;
while (i < len(guesses) && !found) {
let guess = guesses[i];
let result = check_guess(secret, guess);
emit "Guess " + str(guess) + ": " + result;
if (result == "correct") {
found = true;
emit "Found it in " + str(i + 1) + " guesses!";
}
i = i + 1;
}
}
Expected output:
Guess 20: too low
Guess 50: too high
Guess 35: too low
Guess 45: too high
Guess 42: correct
Found it in 5 guesses!
6.14 A Complete Program: Student Grade Analyzer #
Let us bring together everything from Chapters 3 through 6 into one cohesive program:
//
// Student Grade Analyzer
// Combines: variables, types, functions, if/else, while loops
//
fun calculate_average(scores) {
let sum = 0;
let i = 0;
while (i < len(scores)) {
sum = sum + scores[i];
i = i + 1;
}
return sum / len(scores);
}
fun find_max(scores) {
let max = scores[0];
let i = 1;
while (i < len(scores)) {
if (scores[i] > max) {
max = scores[i];
}
i = i + 1;
}
return max;
}
fun find_min(scores) {
let min = scores[0];
let i = 1;
while (i < len(scores)) {
if (scores[i] < min) {
min = scores[i];
}
i = i + 1;
}
return min;
}
fun letter_grade(average) {
if (average >= 90) { return "A"; }
if (average >= 80) { return "B"; }
if (average >= 70) { return "C"; }
if (average >= 60) { return "D"; }
return "F";
}
fun pass_fail(average) {
if (average >= 60) {
return "PASS";
}
return "FAIL";
}
{
// Student data
let names = ["Alice", "Bob", "Charlie", "Diana", "Eve"];
let scores = [92, 78, 85, 64, 95];
emit "========================================";
emit " STUDENT GRADE REPORT ";
emit "========================================";
emit "";
// Individual reports
let i = 0;
while (i < len(names)) {
let grade = letter_grade(scores[i]);
let status = pass_fail(scores[i]);
emit names[i] + ": " + str(scores[i]) + " (" + grade + ") - " + status;
i = i + 1;
}
emit "";
emit "--- Class Statistics ---";
// Class statistics
let avg = calculate_average(scores);
let highest = find_max(scores);
let lowest = find_min(scores);
emit "Average: " + str(avg);
emit "Highest: " + str(highest);
emit "Lowest: " + str(lowest);
emit "Range: " + str(highest - lowest);
// Count passing and failing
let passing = 0;
let failing = 0;
i = 0;
while (i < len(scores)) {
if (scores[i] >= 60) {
passing = passing + 1;
} else {
failing = failing + 1;
}
i = i + 1;
}
emit "";
emit "Passing: " + str(passing) + " / " + str(len(scores));
emit "Failing: " + str(failing) + " / " + str(len(scores));
emit "========================================";
}
Expected output:
========================================
STUDENT GRADE REPORT
========================================
Alice: 92 (A) - PASS
Bob: 78 (C) - PASS
Charlie: 85 (B) - PASS
Diana: 64 (D) - PASS
Eve: 95 (A) - PASS
--- Class Statistics ---
Average: 82.8
Highest: 95
Lowest: 64
Range: 31
Passing: 5 / 5
Failing: 0 / 5
========================================
6.15 Control Flow in Agent Systems #
Before closing this chapter, let us connect control flow to the agent systems you will build in Part III. Every control flow construct you have learned has a direct application in agent programming:
Conditional Routing #
Agents frequently need to decide which specialist to route a query to. This is
an if/else if pattern:
fun route_query(query) {
if (query.contains("billing") || query.contains("invoice")) {
return "billing_agent";
} else if (query.contains("technical") || query.contains("bug")) {
return "tech_agent";
} else if (query.contains("cancel") || query.contains("refund")) {
return "retention_agent";
} else {
return "general_agent";
}
}
Retry Loops #
LLM API calls can fail due to rate limits, network issues, or transient errors. A retry loop handles this:
fun call_with_retry(prompt, max_retries) {
let attempt = 0;
while (attempt < max_retries) {
try {
// In a real program, this would be: Agent.ask(prompt)
let response = "simulated response";
return response;
} catch (e) {
attempt = attempt + 1;
emit "Retry " + str(attempt) + ": " + str(e);
}
}
throw "Failed after " + str(max_retries) + " attempts";
}
Processing Agent Results #
When an agent returns a batch of results, you iterate and filter:
fun extract_approved_items(results) {
let approved = [];
for (result in results) {
if (result == nil) {
continue; // Skip null results
}
if (result.contains("APPROVED")) {
append(approved, result);
}
}
return approved;
}
Guard Clauses for Input Validation #
Before sending data to an agent, validate it with early returns:
fun validate_and_process(input) {
if (input == nil) {
throw "Input cannot be nil";
}
if (typeof(input) != "string") {
throw "Input must be a string, got: " + typeof(input);
}
if (len(input) == 0) {
throw "Input cannot be empty";
}
// Safe to process
return input;
}
These patterns -- routing, retrying, filtering, validating -- appear in every production agent system. The control flow constructs you learned in this chapter are the building blocks.
6.16 Common Mistakes with Control Flow #
Off-by-One Errors #
The most common loop bug. Should it be < or <=?
// Prints 0, 1, 2, 3, 4 (five items)
{
let i = 0;
while (i < 5) {
emit str(i);
i = i + 1;
}
}
// Prints 0, 1, 2, 3, 4, 5 (six items!)
{
let i = 0;
while (i <= 5) {
emit str(i);
i = i + 1;
}
}
Tip: For iterating over a list of length n, use i < n (not i <= n),
because indices go from 0 to n - 1.
Forgetting to Update the Loop Variable #
// INFINITE LOOP -- will never stop
{
let i = 0;
while (i < 5) {
emit str(i);
// MISSING: i = i + 1;
}
}
Wrong Condition Order in else if #
// BUG: score >= 60 matches everything above 60,
// so "B", "C", "D" branches are unreachable
{
let score = 95;
if (score >= 60) {
emit "D"; // This incorrectly catches score=95
} else if (score >= 70) {
emit "C"; // Never reached for 95
} else if (score >= 80) {
emit "B"; // Never reached for 95
} else if (score >= 90) {
emit "A"; // Never reached for 95
}
}
Fix: Always order conditions from most specific to least specific (highest threshold first).
6.17 Chapter Summary #
Control flow gives your programs the ability to make decisions, repeat actions, handle errors, and respond to conditions:
ifexecutes a block when a condition is truthy.elseprovides an alternative block when the condition is falsy.else ifchains multiple conditions; the first matching branch executes.whilerepeats a block as long as a condition remains truthy.for-initerates over lists, sets, maps, strings, ranges, and typed arrays with cleaner syntax than manualwhileloops..enumerate()provides both index and value during iteration.range()comes in three forms:range(n),range(start, end), andrange(start, end, step)for flexible counted iteration.- Destructuring in
for-inloops lets you unpack tuples from.enumerate(),.zip(),.entries(), and other sources directly in the loop header. The...restspread syntax captures remaining list elements. breakexits the innermost loop immediately;continueskips to the next iteration. Both work inwhileandfor-inloops.- The
inoperator tests membership in lists, sets, maps (keys), strings (substrings), and ranges. Usenot infor the negation. - Comprehensions build new collections concisely: list (
[expr for x in iter]), map ({k: v for x in iter}), and set (set(expr for x in iter)). Comprehensions support filters (ifclauses) and nesting (multipleforclauses). try/catch/throwprovides structured error handling --trya risky operation,catchthe error if it fails,throwyour own errors.- FizzBuzz demonstrates combining loops with
if/else if/elseand the modulo operator. - Loop patterns include counting, accumulation, search, filtering, and transformation.
- Nested control flow lets you build complex logic from simple building blocks.
- Agent patterns like routing, retrying, filtering results, and input validation all use the control flow constructs from this chapter.
- Common bugs include off-by-one errors, infinite loops, and misordered conditions.
With variables, types, operators, functions, and control flow, you now have a complete foundation for general-purpose programming in Neam. The next chapters will build on this foundation to explore collections in depth, error handling, and ultimately the agentic AI features that make Neam unique.
Exercises #
Exercise 6.1: Even or Odd
Write a program that iterates through numbers 1 to 20 and emits whether each
number is even or odd. Format: "1: odd", "2: even", etc.
Exercise 6.2: Factorial with a Loop
Rewrite the factorial function from Chapter 5 using a while loop instead of
recursion. Verify that factorial(10) returns 3628800.
Exercise 6.3: FizzBuzz Variations Modify the FizzBuzz program: (a) Change it to use 4 and 7 instead of 3 and 5. (b) Add a fourth category: if divisible by 2, print "Jazz". Be careful about the order of conditions when numbers are divisible by multiple values.
Exercise 6.4: Reverse a List
Write a function reverse_list(items) that returns a new list with the elements
in reverse order. For example, reverse_list([1, 2, 3, 4]) should return
[4, 3, 2, 1]. (Hint: iterate from the end to the beginning, or build the result
by prepending.)
Exercise 6.5: Bubble Sort
Implement bubble sort: repeatedly iterate through a list, swapping adjacent elements
that are in the wrong order, until no swaps are needed. Sort [5, 3, 8, 1, 9, 2]
and emit the sorted result.
Exercise 6.6: Number Pyramid Write a program that emits a number pyramid with 5 rows:
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
(Hint: use a nested loop -- an outer loop for rows and an inner loop for columns.)
Exercise 6.7: Collatz Conjecture The Collatz sequence starts with any positive integer n. At each step: - If n is even, divide it by 2. - If n is odd, multiply it by 3 and add 1.
The conjecture states that this sequence always reaches 1. Write a function
collatz(n) that emits each step until reaching 1 and returns the number of
steps. Test with collatz(27) -- it takes 111 steps.
Exercise 6.8: Password Strength Checker
Write a function check_password(password) that returns a strength rating:
- "weak" if length < 6
- "medium" if length >= 6 and length < 10
- "strong" if length >= 10
Then add a check: if the password contains a digit (check each character using a loop), upgrade "medium" to "strong". Test with several passwords.
Exercise 6.9: Simple Calculator Write a program that takes a list of operations represented as maps:
let operations = [
{"op": "+", "a": 10, "b": 5},
{"op": "-", "a": 20, "b": 8},
{"op": "*", "a": 3, "b": 7},
{"op": "/", "a": 15, "b": 4}
];
Loop through the list, perform each operation, and emit the result in the format:
"10 + 5 = 15".
Exercise 6.10: The Complete Program
Combine everything: write a program that manages a simple inventory. Create a list
of maps, where each map has "name", "quantity", and "price" keys. Write
functions to: (a) find items with quantity below a threshold, (b) calculate the
total value of all inventory, and (c) find the most expensive item. Emit a formatted
report with all results.
Exercise 6.11: Destructuring Challenge You have a list of (name, score) tuples representing students and their test scores:
let students = [("Alice", 88), ("Bob", 95), ("Charlie", 72), ("Diana", 91), ("Eve", 85)];
Using destructuring in a for-in loop, iterate through the list to find and
emit the name and score of the student with the highest score. Your output
should look like: "Top student: Bob with score 95". (Hint: use the
accumulation pattern -- keep track of the best name and best score seen so far
as you iterate.)
Exercise 6.12: Membership Guard
Define a set of allowed file extensions: set("txt", "md", "csv", "json").
Write a function validate_filename(name) that extracts the extension from a
filename (hint: split(name, ".") and take the last element) and uses the in
operator to check whether it is allowed. Test with "data.csv", "script.py",
and "notes.md". Emit "allowed" or "blocked" for each.
Exercise 6.13: Comprehension Workout
Using comprehensions only (no explicit loops), solve each of the following:
(a) Build a list of the first 15 Fibonacci numbers. (Hint: you may need a
helper list and a loop for this one -- comprehensions are not always the
right tool.)
(b) Given let words = ["Neam", "is", "a", "programming", "language"], build
a map from each word to its uppercase form: {"Neam": "NEAM", ...}.
(c) Given let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], use a filtered
comprehension to build a list of numbers whose square is less than 50.
(d) Use a nested comprehension to generate all (row, col) pairs for a 4x4 grid
(rows and cols from 0 to 3).
Exercise 6.14: String Character Counter
Using a for-in loop that iterates over the characters of a string, count
how many vowels (a, e, i, o, u) appear in the string
"The quick brown fox jumps over the lazy dog". Use the in operator to test
whether each character is a vowel.
Exercise 6.15: Map Iteration and Comprehension Given the map:
let inventory = {
"apples": 50,
"bananas": 12,
"cherries": 200,
"dates": 5,
"elderberries": 30
};
(a) Use a for-in loop with .entries() to emit each item and its quantity.
(b) Use a map comprehension to create a new map containing only items with
quantity greater than 20.
(c) Use a list comprehension to build a sorted list of item names that have
more than 6 characters.