From problem to specification
Before writing code, pin down what must be built. A problem specification states:
- the inputs (and their valid ranges),
- the processes the system must carry out,
- the outputs expected,
- constraints (time, memory, platform, users),
- success criteria that can be tested.
Example: a canteen pre-order app. Inputs: student ID, chosen meals, pick-up time. Output: confirmation and a daily list for the kitchen. Constraint: orders close at 10:00. Success criterion: an order placed after 10:00 is rejected with a message.
The four thinking tools
- Decomposition: split a big problem into smaller ones that can be solved and tested separately. Canteen app: menu display, ordering, payment record, kitchen report, notifications.
- Abstraction: keep only the details that matter for the purpose. A meal in the system needs a name, price, allergens and quantity left; the colour of the plate is irrelevant.
- Pattern recognition: notice where sub-problems are alike so one solution can be reused. "Total of today's orders" and "total of this week's orders" are the same summing pattern over different lists.
- Algorithm design: write the exact, finite, ordered steps that solve each part.
A good algorithm is unambiguous, finite (it always stops), has defined inputs and outputs, and is correct for every valid input including edge cases.
Representing algorithms
Flowchart symbols: oval = start/stop, parallelogram = input/output, rectangle = process, diamond = decision (two labelled exits), arrows = flow.
flowchart TD
S([Start]) --> I[/Input mark/]
I --> D{mark >= 0 and mark <= 100?}
D -- No --> E[/Output 'invalid'/] --> I
D -- Yes --> P{mark >= 50?}
P -- Yes --> O1[/Output 'pass'/]
P -- No --> O2[/Output 'fail'/]
O1 --> X([Stop])
O2 --> XThe same logic in Python:
mark = int(input("Mark: "))
while mark < 0 or mark > 100:
print("invalid")
mark = int(input("Mark: "))
print("pass" if mark >= 50 else "fail")Tracing algorithms
A trace table has one column per variable (plus conditions and output) and one row each time a value changes. It is how you prove what code does without running it.
a = 48
b = 18
while b != 0:
r = a % b
a = b
b = r
print(a)| a | b | r | b != 0 | Output |
|---|---|---|---|---|
| 48 | 18 | - | True | |
| 18 | 12 | 12 | True | |
| 12 | 6 | 6 | True | |
| 6 | 0 | 0 | False | |
| 6 |
Purpose: it finds the greatest common divisor (Euclid's method). Being able to state the purpose after tracing is a frequent exam part.
Testing an algorithm
Choose test data in three kinds. For a function accepting ages 13 to 19 inclusive:
| Kind | Example values | Expected |
|---|---|---|
| Normal | 16 | accepted |
| Boundary | 13, 19 (and just outside: 12, 20) | accept, accept, reject, reject |
| Invalid / erroneous | -4, "abc" | rejected |
Boundary values matter because off-by-one mistakes (< instead of <=) only show up at the edges.
Common mistakes
- Writing a trace table with only the final values instead of every change.
- Updating variables in the wrong order when tracing (follow the code line by line).
- Giving abstraction examples that are actually needed (a room's capacity is not removable for a booking system).
- Decision diamonds with unlabelled exits, or loops drawn without a way out.
- Listing only valid data when asked for test data.
Exam tips
- When asked to "outline decomposition", name concrete sub-problems from the scenario, not a definition.
- Use integer division and modulo carefully:
17 // 5 = 3,17 % 5 = 2. - After a trace, state the output clearly on its own line.
- For "identify the condition", quote the exact condition from the code.