Programming Neam
📖 18 min read

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:


🌎 Real-World Analogy: Control Flow Is GPS Navigation

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:

neam
{
  let temperature = 35;

  if (temperature > 30) {
    emit "It's hot outside!";
  }
}

The general form is:

text
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:

neam
{
  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:

text
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:

neam
{
  let age = 16;

  if (age >= 18) {
    emit "You can vote.";
  } else {
    emit "You cannot vote yet.";
  }
}

Expected output:

text
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:

neam
{
  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:

text
Grade: B
score >= 90?
"A"
score >= 80?
"B"
score >= 70?
"C"
score >= 60?
"D"
"F"

How it works:

  1. Neam evaluates the first condition (score >= 90).
  2. If true, it executes that block and skips all remaining else if / else blocks.
  3. If false, it moves to the next condition (score >= 80).
  4. This continues until a condition is true or the else block is reached.
  5. 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:

neam
{
  let i = 1;

  while (i <= 5) {
    emit "Count: " + str(i);
    i = i + 1;
  }
}

Expected output:

text
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5

The general form is:

text
while (<condition>) {
  // body -- executed repeatedly
}

Execution flow:

  1. Evaluate the condition.
  2. If true, execute the body.
  3. Go back to step 1.
  4. 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:

  1. Initialization: Set up the loop variable before the loop (let i = 1).
  2. Condition: Test whether to continue (i <= 5).
  3. 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:

neam
// 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:

neam
{
  let fruits = ["apple", "banana", "cherry", "date"];

  for (fruit in fruits) {
    emit "I like " + fruit;
  }
}

Expected output:

text
I like apple
I like banana
I like cherry
I like date

The general form is:

text
for (<variable> in <iterable>) {
  // body -- executed once per element
}
Get next item from
iterable
Items
remain?
Bind item to loop
variable
Execute loop body

Compare this with the equivalent while loop:

neam
// 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:

Iterable Types

List iteration (the most common case):

neam
{
  for (item in [1, 2, 3]) {
    emit str(item);
  }
}

Set iteration -- visits every unique element, but order is not guaranteed:

neam
{
  let tags = set("urgent", "bug", "backend");
  for (tag in tags) {
    emit f"Tag: {tag}";
  }
}

String iteration -- visits each character:

neam
{
  for (ch in "Neam") {
    emit ch;
  }
}

Expected output:

text
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:

neam
{
  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():

neam
{
  let colors = ["red", "green", "blue"];

  for ((i, color) in colors.enumerate()) {
    emit str(i) + ": " + color;
  }
}

Expected output:

text
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:

neam
{
  // 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:

text
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.

Common Mistake: Forgetting range() for Counted Loops

Forgetting range() for Counted Loops

A frequent beginner mistake is trying to iterate over a number directly instead of using range():

neam
// 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:

neam
{
  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:

neam
{
  let scores = {
    "Alice": 95,
    "Bob": 87,
    "Carol": 92
  };

  for ((name, score) in scores.entries()) {
    emit f"{name} scored {score}";
  }
}

Expected output:

text
Alice scored 95
Bob scored 87
Carol scored 92

Iterating over values only:

neam
{
  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:

neam
{
  // 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:

text
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:

neam
{
  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:

neam
{
  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:

text
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:

Both keywords work in while loops and for-in loops.

Loop start
Loop start
Iteration
┌────────┐
│ break; │──
└────────┘
(code below
skipped)
break;
Iteration
┌────────┐
│continue│──
└────────┘
(code below
skipped)
continue
Next item

break: Exit Early #

neam
{
  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:

text
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 #

neam
{
  // 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:

text
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:

neam
{
  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:

text
Process halted: rejection found
Approved before halt: 2

break and continue in while Loops #

Both break and continue work identically in while loops:

neam
{
  // 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:

neam
{
  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:

text
(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:

neam
{
  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:

value in collection

in with Lists #

neam
{
  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:

text
7 is prime

in with Maps #

When used with a map, in checks whether the value is a key in the map:

neam
{
  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:

text
host = localhost

in with Strings #

For strings, in performs a substring search:

neam
{
  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:

text
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:

neam
{
  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:

neam
{
  let forbidden = set("DROP", "DELETE", "TRUNCATE");
  let command = "SELECT";

  if (command not in forbidden) {
    emit f"'{command}' is safe to execute";
  }
}

Expected output:

text
'SELECT' is safe to execute

Practical Example: Input Validation #

The in operator is especially useful for validating inputs before processing:

neam
{
  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:

text
Unknown command: 'pause'
Valid commands: ["help", "status", "run", "stop", "restart"]
📝 in vs. .contains()

The in operator and the .contains() method do the same thing, but in reads more naturally in conditionals:

neam
// 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.

📝 Note

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).

4 lines of code
1 line of code

List Comprehensions #

The most common form. Build a new list by applying an expression to each element of an iterable:

neam
{
  // 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:

text
[<expression> for <variable> in <iterable>]

This is equivalent to the loop:

neam
{
  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:

neam
{
  // 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:

text
[<expression> for <variable> in <iterable> if <condition>]

Map Comprehensions #

Build a map (dictionary) from an iterable. Each iteration produces a key-value pair:

neam
{
  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:

text
{<key_expr>: <value_expr> for <variable> in <iterable>}

Set Comprehensions #

Build a set from an iterable, automatically removing duplicates:

neam
{
  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:

neam
{
  // 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:

neam
{
  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:

neam
{
  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.

⚠️ Edge Case: Empty Comprehensions

Empty Comprehensions

If the iterable is empty or the filter excludes everything, the result is simply an empty collection:

neam
{
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 #

neam
{
  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:

neam
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

neam
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

neam
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);
  }
}
📝 Note

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:

Here is the Neam implementation from examples/04_control_flow.neam:

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:

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.

🎯 Try It Yourself

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 #

text
=== 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:

neam
{
  // 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:

neam
{
  // 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.

Look for a value that meets a condition:

neam
{
  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:

text
Found 9 at index 3

Pattern 4: Filtering #

Build a new collection containing only elements that match a condition:

neam
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:

text
Even numbers: [2, 4, 6, 8, 10]

Pattern 5: Transformation #

Apply an operation to each element and collect the results:

neam
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:

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:

text
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.

🎯 Try It Yourself

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 #

neam
{
  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:

text
=== 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 #

neam
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:

text
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:

neam
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 #

neam
{
  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:

neam
{
  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 #

neam
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:

text
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:

neam
//
// 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:

text
========================================
        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:

neam
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:

neam
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:

neam
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:

neam
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 <=?

neam
// 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 #

neam
// INFINITE LOOP -- will never stop
{
  let i = 0;
  while (i < 5) {
    emit str(i);
    // MISSING: i = i + 1;
  }
}

Wrong Condition Order in else if #

neam
// 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:

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:

text
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:

neam
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:

neam
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:

neam
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.

Start typing to search...