---
title: 'Structural Programming'
source: 'https://academia.sh/en/courses/programming-fundamentals/structural-programming'
course: 'Programming Fundamentals'
language: en
updated: '2026-08-17T18:08:26+00:00'
license: 'CC BY-SA 4.0'
---

# Structural Programming

Building programs with sequence, selection, and iteration; abandoning the jump statement and top-down decomposition.

The structures used throughout this course — sequential statements, conditional
branching, loops, and functions — belong to a particular organizing philosophy. That
philosophy is called **structural programming**, and its looking natural does not mean
it was accepted without dispute; it is the outcome of a debate.

This topic compares three organizing approaches. Each answers differently the question
of what units a program is divided into and how those units communicate. All three can
solve the same problem; their differences show up as programs grow.

## Three Structures Suffice

The core claim of structural programming is this: every computable program can be
written using only three control structures.

- **Sequence:** Statements executed one after another.
- **Selection:** Following one of two paths depending on a condition.
- **Iteration:** Repeating a block depending on a condition.

That these three suffice is a formally proven result. Its practical meaning is that no
fourth structure — in particular, a statement that jumps to an arbitrary point in the
program — is needed.

## The Problem with the Jump Statement

Early languages included a statement that allowed jumping directly to a desired line of
the program. At the hardware level this is natural: as seen in the previous course, a
branch instruction is nothing more than writing a new address into the program counter.

The problem shows up in readability. In a program written with unrestricted jumps, which
paths can reach a given line cannot be determined by reading the text. Reasoning about
the value of a variable at that point requires examining every path that reaches it, and
the number of paths grows quickly with the number of jumps.

Structural control structures solve this problem by definition: each structure has
exactly one entry and one exit. A block is entered only at its start and left only at
its end. This constraint makes it possible to understand what a block does by looking at
the block itself.

Early-exit statements — breaking out of a loop, returning early from a function — are a
controlled relaxation of this constraint. What sets them apart from unrestricted jumping
is that their targets are fixed and structural: an exit always goes to the end of the
structure it is inside.

## Top-Down Decomposition

The second component of structural programming is breaking the problem down step by
step. The process starts at the top: the program is broken into a few high-level steps;
each step is broken into its own substeps; the breaking stops once each piece is small
enough to be written directly.

The decomposition in the pseudocode lesson follows exactly this method. For the running
problem:

```
summarize(measurements)
├── validate(measurements)     → empty?, valid?
├── add_up(measurements)       → total
├── find_largest(measurements) → largest
└── average(total, count)      → average
```

Each node corresponds to a procedure. The top-level procedure knows what the lower-level
ones do; it does not know how they do it. This information hiding limits how far a
change propagates: when the inside of `find_largest` changes, its caller is unaffected.

## The Structural Solution to the Common Problem

```python
def validate(measurements: list[int]) -> None:
    """Checks that the measurement list is processable."""
    if not measurements:
        raise ValueError("measurement list is empty")


def add_up(measurements: list[int]) -> int:
    total = 0
    for measurement in measurements:
        total += measurement
    return total


def find_largest(measurements: list[int]) -> int:
    largest = measurements[0]
    for measurement in measurements:
        if measurement > largest:
            largest = measurement
    return largest


def summarize(measurements: list[int]) -> tuple[float, int]:
    """Returns the average and the largest value."""
    validate(measurements)
    return add_up(measurements) / len(measurements), find_largest(measurements)


print(summarize([12, 18, 7, 25, 14]))     # (15.2, 25)
```

The structure of the solution matches the decomposition tree exactly. Each procedure
does one thing, can be tested on its own, and its name says what it does.

## Grouping Procedures

As decomposition continues, the number of procedures grows into a flat list. A second
organizing layer is needed: related procedures are collected into the same **module**.

A module is the practical counterpart of the namespace defined in the scope lesson.
Procedures that work with measurement data sit in one module, reporting procedures in
another. A module boundary does two things: it separates names, and it documents which
procedures change together.

A module also has an interface. Procedures exposed to the outside are separated from
helpers used only internally; this separation is the same internal-versus-external
visibility distinction from the previous course's binding lesson. Exposing few
procedures to the outside makes changing the inside of a module easier.

This organization is what keeps structural programming workable in large programs: the
program is no longer read as a hierarchy of procedures but as a **relationship between
modules**. The scaled-up version of the same idea — components and layers — is the
subject of the **Software Architecture** curriculum.

## Data and Procedures Kept Separate

The distinguishing feature of this approach is that **data and the procedures that
operate on it are defined separately.** The `measurements` list is a data structure;
`add_up` and `find_largest` are independent procedures that operate on it. Data travels
to procedures as an argument.

In small programs this arrangement is clear and needs no extra concept. As a program
grows, two difficulties appear.

First, which procedures a data structure can be used with is written nowhere in the
code; the relationship rests on convention alone. Passing the same list to the wrong
procedure is an error the language does not prevent.

Second, the rules of a data structure — its invariants — are preserved in a scattered
way. The rule "the measurement list is never empty" has to be re-checked in every
procedure that uses the list; when one procedure forgets, the rule is silently violated.

These two difficulties are the starting point of the next lesson: the idea of gathering
data and the behavior that operates on it into a single unit.

## The Testability Gain

A less-discussed but decisive consequence of decomposition is testability. A single long
procedure can only be tested by running it from start to finish; decomposed procedures
can each be tested independently.

In the solution above, `find_largest` can be tested without reading a file, writing to
the screen, or depending on any other step: a list is given, the returned value is
compared against the expected one. Edge cases — a single-element list, all equal values
— are tested with the same ease.

This gain grows when procedures are **pure**: a procedure that looks at nothing beyond
its input and leaves no trace outside requires no setup to test. The same observation
becomes the core idea of a paradigm in this topic's third lesson.

## Where Structural Programming Stands

Structural programming was not invalidated by the paradigms that followed; it became
the ground they were built on. The method of an object-oriented class and the function
of a functional language are both written, on the inside, with sequence, selection, and
iteration.

For this reason, the question of "which paradigm" is asked not at the level of control
structures but at the level of **what units a program is divided into**. The structural
answer is the procedure.

## Summary

- Structural programming holds that every program can be written with sequence,
  selection, and iteration; these three suffice.
- Unrestricted jumping makes it impossible to trace which paths reach a given point;
  structural control structures have a single entry and a single exit.
- Early-exit statements are a controlled relaxation of this constraint because their
  target is fixed.
- Top-down decomposition breaks a problem into procedures; the top-level procedure knows
  what the lower level does, not how it does it.
- Keeping data and procedures separate is simple in small programs, but in large
  programs it makes preserving the relationship and the invariants harder.
- Decomposed procedures can be tested independently; related procedures are grouped
  into modules to establish name separation and a boundary for change.

## Next Step

Gathering data and the procedures that operate on it into a single unit answers directly
the two difficulties that emerged at the end of this lesson. The next lesson introduces
the basic concepts of the object-oriented approach and solves the same problem again,
this time through an object.
