Control Flow

Control flow determines the order in which program instructions execute — the difference between a fixed sequence and a program that responds intelligently to conditions and data.

Why This Matters

A program without control flow would execute every instruction exactly once, in the order written, and stop. It could do no more than a pocket calculator. Control flow — the ability to repeat instructions, skip sections, and choose between alternatives — is what makes programs capable of solving real-world problems.

For rebuilders establishing a computing foundation, control flow is the vocabulary of programming logic. Before you can teach others to program, before you can write tools that other people can use, you need to articulate how execution paths work: how a loop terminates, why a condition causes different behavior on different runs, how function calls stack up and return.

Understanding control flow at both the high-level (IF-THEN, FOR, WHILE) and the assembly level (conditional jumps, return addresses on the stack) gives you complete understanding of how programs work and enables you to reason about any program’s behavior.

Sequential Execution

The default mode of execution is sequential: instructions execute one after another in the order they appear in memory. The program counter advances to the next instruction after each one completes.

This is the baseline. Every deviation from sequential execution — every jump, call, or return — is an exception to this default. Most program code is sequential; control flow structures are the organized exceptions.

Understanding that sequential execution is the default helps when debugging. When you see unexpected behavior, ask: “What control flow structure could have caused execution to skip or repeat this code?” This immediately focuses your investigation.

Conditional Execution

Conditional execution lets a program take different paths based on a comparison or state test. The fundamental form is IF-THEN-ELSE:

IF temperature > 100 THEN
    PRINT "BOILING"
ELSE
    PRINT "NOT YET"
END IF

The condition — temperature > 100 — evaluates to true or false. If true, the THEN branch executes; if false, the ELSE branch executes. The ELSE branch is optional; without it, the program simply skips the body when the condition is false.

In assembly language, this compiles to a comparison instruction that sets processor flags, followed by a conditional jump that either bypasses the THEN body or does not.

Nested conditions are conditions inside other conditions:

IF X > 0 THEN
    IF X < 100 THEN
        PRINT "IN RANGE"
    END IF
END IF

This is equivalent to IF X > 0 AND X < 100. Deep nesting makes code hard to read; limit nesting to two or three levels in practice.

CASE (or SELECT or SWITCH) structures dispatch to one of several code blocks based on a value:

SELECT CASE MODE
    CASE 0: GOSUB IDLE_ROUTINE
    CASE 1: GOSUB MEASURE_ROUTINE
    CASE 2: GOSUB REPORT_ROUTINE
    CASE ELSE: PRINT "UNKNOWN MODE"
END SELECT

In assembly, CASE is typically implemented as a comparison and jump series. For dense integer cases, a jump table (array of addresses indexed by the case value) is faster: load the case value, look up its address in the table, and jump to that address.

Loops

Loops repeat a section of code. The three fundamental loop forms are:

While loop: Tests the condition before each iteration. If the condition is initially false, the body never executes.

WHILE battery_level > 10
    TRANSMIT_DATA()
    WAIT 1000
WEND

Assembly: a conditional jump at the top of the loop body jumps past the loop when the condition becomes false.

Do-while loop (called REPEAT-UNTIL in some languages): Tests the condition after each iteration. The body always executes at least once.

DO
    READ_SENSOR(value)
LOOP UNTIL value > 0    ' repeat until valid reading

Assembly: the conditional jump is at the bottom of the loop body, jumping back to the top if the condition is not met.

For loop: Counts a fixed number of iterations using a counter variable.

FOR I = 1 TO 10
    PROCESS(I)
NEXT I

Assembly: initialize the counter, test it at the top or bottom, execute the body, increment/decrement the counter, and loop. Many CPUs have dedicated decrement-and-branch instructions optimized for this pattern.

Loop Control: Break and Continue

In languages that support them, BREAK (or EXIT) immediately terminates the current loop and resumes after it. CONTINUE (or NEXT in some languages) skips the rest of the current iteration and moves to the next.

FOR I = 0 TO 100
    IF DATA(I) = 0 THEN EXIT FOR   ' stop at first zero
    PROCESS DATA(I)
NEXT I

In assembly, BREAK is an unconditional jump to the instruction after the loop. CONTINUE is an unconditional jump to the loop test (for while loops) or the counter update (for for loops).

Infinite loops — loops designed never to terminate — are the normal structure for embedded systems that run continuously:

DO
    CHECK_SENSORS()
    UPDATE_OUTPUTS()
    HANDLE_SERIAL()
LOOP

The loop exits only when power is cut. This is correct behavior for a device that should run as long as it is powered.

Subroutine Calls and Returns

Subroutines (also called procedures, functions, or routines) give code a name so it can be invoked from multiple places. This is the fundamental mechanism for reuse.

A GOSUB (CALL) instruction:

  1. Pushes the return address (the address of the instruction after the call) onto the stack
  2. Jumps to the subroutine’s first instruction

A RETURN instruction:

  1. Pops the return address from the stack
  2. Jumps to that address, resuming execution where the call was made
MAIN:
    GOSUB MEASURE    ; call subroutine
    GOSUB REPORT     ; call another subroutine
    END

MEASURE:
    READ_SENSOR()
    RETURN

REPORT:
    PRINT_RESULTS()
    RETURN

The stack makes nested and recursive calls work correctly: each call pushes a return address, and each return pops the most recent one, so calls unwind in the right order.

Recursion

Recursion is a subroutine calling itself. It is a powerful technique for problems that have self-similar structure, like traversing a tree or computing factorial.

FUNCTION FACTORIAL(N)
    IF N <= 1 THEN RETURN 1
    RETURN N * FACTORIAL(N - 1)
END FUNCTION

Each recursive call pushes a new return address and a new set of local variables onto the stack. The recursion terminates when it reaches the base case (N 1) and returns; each return unwinds one level.

Recursion can overflow the stack if the recursion depth is too great. For constrained systems with limited stack space, prefer iterative solutions for performance-critical or deeply recursive code. A recursive algorithm can usually be rewritten iteratively using an explicit stack.

Control Flow and Programs Structure

Well-structured programs have clear, predictable control flow. Each loop has a single entry point (the top) and exits at well-defined points. Each function has one entry point. Deeply nested conditions are extracted into named functions.

Avoid spaghetti code — programs where GOTO statements jump arbitrarily across large distances, making the flow impossible to follow without tracing execution step by step. This pattern was common in early BASIC programs and made maintenance a nightmare.

The structured programming insight (from Dijkstra, Wirth, and others in the 1960s-70s) is that any program can be written using only three control flow patterns: sequence, selection (IF), and iteration (WHILE/FOR). GOTO is never necessary and usually makes programs harder to understand.

Practical Notes for Rebuilders

When teaching programming to new learners, spend significant time on control flow before any other concept. The ability to trace a program’s execution path — to simulate a computer in your head — is the core skill of programming. Practice by tracing programs on paper with a pencil, tracking the value of each variable at each step.

Draw flowcharts for complex control flow before writing code. A flowchart makes visible the paths a program can take, the conditions that determine each path, and the loops that repeat. Errors in logic are much easier to spot in a visual diagram than in code.

For embedded systems, document the expected control flow through each major operating mode. “Normal operation runs the main loop at 10 Hz; interrupt handler fires up to 100 times per second for each sensor” is the kind of description that prevents timing bugs.