Lesson 09 / 18
Pseudocode
Writing a solution independent of any language, testing it with a manual trace table, and identifying edge cases.
Contents
The previous three lessons introduced control structures directly through code. For a complex problem, the order reverses: the solution is designed first, then translated into code. Designing directly in a language’s syntax requires thinking about two different problems — “what to do” and “how to write it” — at the same time.
This lesson defines the ways to write a solution independent of any language, and to test it before putting it into code.
What Pseudocode Is
Pseudocode is a way of writing a solution close to natural language, but with enough precision that its steps leave no ambiguity. It is not compiled, it is not run; it is read.
It has no strict syntax, but common conventions exist:
- An arrow, or the phrase “let … be”, is used for assignment.
- Blocks are shown with indentation.
- Loops and conditions are written out plainly: for each measurement, if … then.
- Language-specific details — library names, type declarations, memory management — are left out.
The measure is this: two people reading the pseudocode must understand the same behavior — this text, written before any code, is where the solution is discussed. If ambiguity remains, it is not detailed enough; if it approaches a language’s syntax, it is more detailed than it needs to be.
Analyzing the Problem
The problem must be understood before writing pseudocode. Four questions do this work:
- What is the input? Its type, range, and validity conditions.
- What is the output? A single value, a collection, or a side effect.
- Which cases are special? Empty input, single-element input, equal values, invalid values.
- How would I solve it by hand? Putting the steps into words over a small example reveals the solution itself.
The fourth question is the most productive. Someone computing the average of five measurements by hand uses an accumulator pattern without realizing it: the numbers are summed in order, then divided by five.
The Shared Problem in Pseudocode
The course’s shared problem — the average and the maximum — is written in pseudocode like this:
INPUT: measurements (list of numbers)
OUTPUT: average (number), largest (number)
if measurements is empty
report error: "empty input"
total ← 0
largest ← measurements[0]
for each measurement in measurements
total ← total + measurement
if measurement > largest
largest ← measurement
average ← total / number of measurements
return average and largest
This text contains no detail belonging to any language; yet every step is single and
leaves no room for debate. Notice that the empty-input check is done before the
loop: initializing the largest variable with the first element assumes the list is
not empty.
The same pseudocode uses two accumulators in a single loop instead of two loops. This is a design decision: the data is walked once, and two results are produced together.
Tracing by Hand
A solution’s correctness can be tested before it is translated into code, using a trace table. In the table, each row shows an iteration, each column a variable.
For the input [12, 18, 7]:
| Iteration | measurement | total | largest |
|---|---|---|---|
| start | — | 0 | 12 |
| 1 | 12 | 12 | 12 |
| 2 | 18 | 30 | 18 |
| 3 | 7 | 37 | 18 |
| result | — | 37 | 18 |
The average is ; the largest is . The results match the hand calculation.
A trace table catches two kinds of errors: a wrong initial value and a wrong update
order. For example, if largest were initialized to zero, the table would
immediately show the result coming out as zero for an input where all measurements
are negative.
The method is slow and cannot be applied to large inputs; that is not its purpose either. An input of three or four elements is enough to expose most logic errors. The same table can also be built on a running program during debugging: printing the values of variables on every iteration is having the program itself produce the trace table.
Edge Cases
A solution working on ordinary input is not enough. Edge cases are where a solution’s assumptions break down:
| Edge case | Behavior in this problem |
|---|---|
| Empty list | Average is undefined; an error is reported |
| Single element | Average is that element, largest is that element |
| All values equal | Largest is that value |
| All values negative | Largest is the least negative one |
| Very large values | The total can exceed the type’s range |
The last row connects to the previous course’s overflow lesson: a total accumulating in a fixed-width type produces a silently wrong result if it goes out of range.
Listing edge cases is also listing the tests that will be written later. In the software quality curriculum, this is called boundary value analysis.
Decomposing the Problem into Subproblems
The shared problem fit into a single pseudocode block. Real problems do not; a solution can neither be written nor traced as a single piece.
The standard method is decomposition: the problem is broken into subproblems, each doing a single job. A program working with measurement data can be decomposed like this:
read(source) → raw lines extract(raw lines) → valid measurements, skipped count summarize(measurements) → average, largest report(average, largest, skipped) → output text
Each of the four steps can be written separately, tested separately, and when one changes, the others are unaffected. The criterion for splitting is whether a step can be described in a single sentence: a step doing two jobs joined by “and” should probably be split in two.
The counterpart of this decomposition in code is functions. Naming a step in pseudocode corresponds to defining a function in code; decomposition is therefore a design task done before any code is written.
From Pseudocode to Code
Translation means writing the target language’s counterpart of every line of pseudocode:
def summarize(measurements: list[int]) -> tuple[float, int]: """Returns the average and the largest of the measurements.""" if not measurements: raise ValueError("empty input") total = 0 largest = measurements[0] for measurement in measurements: total += measurement if measurement > largest: largest = measurement return total / len(measurements), largest print(summarize([12, 18, 7])) # (12.333333333333334, 18) print(summarize([12, 18, 7, 25, 14])) # (15.2, 25)
The first output matches the values computed by hand in the trace table — this is proof that the translation was done correctly.
During translation, decisions that were not in the pseudocode appear: how to report an error, how to return two values together, what the types will be. These decisions belong to the language and are not a deficiency of the pseudocode; they are details the pseudocode deliberately leaves out.
Summary
- Pseudocode is a way of writing a solution independent of any language and without leaving ambiguity; its measure is that two readers understand the same behavior.
- Problem analysis is done with four questions: input, output, special cases, and the by-hand solution.
- A trace table tests a solution without running it; a wrong initial value and a wrong update order are caught this way.
- The list of edge cases is also the list of tests to be written later.
- The decisions that appear during translation from pseudocode to code belong to the language; pseudocode deliberately leaves these details out.
Next Step
The solution in the last example was wrapped in a def block and named
summarize. This construct has been used without explanation up to now. The next
topic defines functions: how parameters and return values work, in what region names
are valid, and what happens when a function calls itself.
To keep your progress and take notes, Log in
My notes
Log in to take notes.