You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Thinking procedurally is the art of turning one big, intimidating problem into a set of small, solvable ones — and then working out the order in which those small problems must be tackled. Nobody solves "build a school management system" in a single thought; they break it into student records, timetabling, attendance and exams, break each of those down again, and keep going until every remaining piece is small enough to code directly. This deliberate, top-down breaking-down is decomposition, and it is the single most practical computational-thinking skill you will use in the NEA programming project and in every real software system.
But decomposition alone is not enough. Once you have the pieces you must reason about how they fit together: which steps depend on which, what must happen before what, and how data flows between the parts. Order is not a detail — getting it wrong ("dispense the cash then check the balance") breaks the system as surely as a coding error. This lesson develops the full H446 picture: decomposition into sub-problems and procedures, the importance of sequence and dependencies, top-down stepwise refinement, and the structure diagrams that record a decomposition visually.
This lesson addresses H446 section 2.1.3 — Thinking Procedurally. The specification expects you to:
Procedural thinking is the bridge between abstract problem-solving and concrete code: the procedures you identify here become the subroutines you write in the programming module (2.2.1), and the dependency reasoning here reappears as scheduling and concurrency in 2.1.5 and the systems modules.
Procedural thinking answers three linked questions about any problem:
A handy mental test: if you can describe a solution as "first do A, then do B, then for each item do C…", you are thinking procedurally. The procedures (A, B, C) are the what; the "first/then/for each" is the order. Both halves matter, and the rest of this lesson takes each in turn.
Decomposition is breaking a large, complex problem into smaller, more manageable sub-problems. Each sub-problem can be solved on its own — or, if it is still too big, decomposed again. You stop when every leaf is small enough to implement directly as a single procedure.
The clearest way to see decomposition is as a tree that fans out from the whole problem at the top to concrete tasks at the bottom. Here is a school management system decomposed top-down:
flowchart TB
SMS["School Management System"]
SMS --> SR["Student Records"]
SMS --> TT["Timetabling"]
SMS --> AT["Attendance"]
SMS --> EX["Exams"]
SR --> SR1["Add student"]
SR --> SR2["Edit student"]
SR --> SR3["Search students"]
TT --> TT1["Create timetable"]
TT --> TT2["Detect clashes"]
AT --> AT1["Mark register"]
AT --> AT2["Generate absence report"]
EX --> EX1["Enter results"]
EX --> EX2["Calculate grades"]
EX --> EX3["Print reports"]
Read the tree top-to-bottom for increasing detail: the root is the whole system, the second row is its major sub-systems, and the leaves are concrete tasks small enough to code. "Detect clashes" or "Calculate grades" is a well-defined procedure you could sit down and write today — which is exactly the point at which decomposition stops.
| Benefit of decomposition | Why it follows |
|---|---|
| Manageable complexity | A small, focused sub-problem is far easier to reason about than the whole |
| Parallel development | Different people can build different sub-systems at the same time |
| Independent testing | Each component can be tested on its own before integration |
| Reusability | A well-isolated component (e.g. "validate date") can be reused elsewhere |
| Easier maintenance | A bug is localised to one small, well-defined procedure |
Decomposition and the procedural abstraction of lesson 2.1.1 are two sides of one coin: every box in the tree will become a named procedure whose internals are hidden from its siblings. Decomposition decides what the procedures are; procedural abstraction lets each one be used without knowing how the others work.
A crucial judgement in decomposition is knowing when to stop and when to split further. Stop refining a box when it represents a single, well-defined job you could implement directly — a procedure with one clear purpose, a sensible name, and a tidy set of inputs and outputs. Keep splitting while a box still describes a whole sub-system ("handle exams") rather than a concrete task ("calculate a grade from a mark"). Two warning signs tell you a decomposition has gone wrong. If a box's description needs the word "and" to capture everything it does ("validate the date and update the record and send the email"), it is doing too much and should be split. Conversely, if you have shattered something genuinely simple into a dozen one-line boxes, you have over-decomposed and merely added clutter. Good decomposition lands each leaf at the level of a single responsibility — neither a sprawling sub-system nor a trivial fragment.
How do you find the right components when staring at a problem description? Several systematic techniques help, so the decomposition is not just guesswork.
| Technique | What it finds |
|---|---|
| Noun analysis | Underline the nouns — they tend to become data/objects (Customer, Order, Product) |
| Verb analysis | Underline the verbs — they tend to become procedures (register, search, checkout) |
| Input–Process–Output | Identify what goes in, what transformation happens, and what comes out |
| User stories | "As a [user] I want to [action] so that [benefit]" — each is a candidate feature |
Applied to an online-shopping system, noun and verb analysis quickly yields a first decomposition:
| Nouns → data/objects | Verbs → procedures |
|---|---|
| Customer | register, log in, log out |
| Product | search, view, add to basket |
| Basket | view, change quantity, remove item |
| Order | place, confirm, track |
| Payment | enter details, validate, process |
The nouns suggest the structures (classes, records, tables); the verbs suggest the procedures that act on them. This is a fast, defensible way to turn a paragraph of requirements into the first layer of a structure diagram.
These techniques are not mutually exclusive — strong designers use several together and cross-check the results. Noun and verb analysis is excellent for a first pass, but it can over-produce (not every noun deserves its own structure) and under-produce (some procedures are implied rather than stated, such as "validate input" or "handle errors"). Input–Process–Output is a good sanity check because it forces you to confirm that every output can actually be produced from the available inputs, exposing missing components — if a payslip must show net pay but no procedure computes it, IPO analysis catches the gap. User stories add the user's perspective, ensuring the decomposition covers what people actually need to do rather than only what is technically obvious. Running two or three of these techniques over the same problem and reconciling them gives a far more complete component list than any single technique alone.
Decomposition tells you what the pieces are; you still have to determine when each runs. Most sub-problems cannot be executed in just any order, because of dependencies.
Key Term: A dependency exists when one component needs the output or completion of another before it can run. If B depends on A, then A must finish first.
There are four common ordering relationships between tasks:
| Ordering | Meaning | Example |
|---|---|---|
| Sequential | One after another, in a fixed order | Validate input → process → display result |
| Conditional | A step runs only if a condition holds | Process payment only if the basket is non-empty |
| Iterative | A step repeats until a condition is met | Keep asking for input until it is valid |
| Parallel | Independent steps may overlap in time | Load page text while images download |
Dependencies dictate which orderings are legal. In a checkout, for example:
| This step | depends on | so it must come after |
|---|---|---|
| View dashboard | logging in | the login succeeding |
| Place order | items in basket | at least one "add to basket" |
| Process payment | payment details entered | the details being captured |
Getting order wrong is a logic failure, not a syntax one — the program runs but does the wrong thing. Dispensing cash before checking the balance, or charging a card before confirming stock, are classic order errors. Identifying the dependency graph during planning is how you avoid them. Where two steps have no dependency between them, they are independent and could in principle run in parallel — a connection that leads directly into concurrent thinking (2.1.5).
A useful way to picture dependencies is as a directed graph: draw an arrow from A to B whenever B depends on A. The arrows then tell you every legal order — any sequence that never runs a task before something it points from. (Computer scientists call producing such an order a topological sort of the dependency graph, an idea you meet again in the algorithms module and which underlies the build tools and task schedulers used in real software.) When the graph is a simple chain — A→B→C→D — there is exactly one legal order. When several tasks share no arrows, there are many legal orders, and you are free to choose, including running them at the same time. The richer the dependency graph, the more the order is constrained for you; the sparser it is, the more freedom (and concurrency opportunity) you have. Either way, the dependencies — not your intuition about "what feels first" — are the authority on order, and writing them down explicitly is what turns a guess into a correct plan.
Top-down design (also called stepwise refinement) is the method that produces a decomposition tree. You start with the whole problem as a single statement and repeatedly refine each part into more detailed sub-parts, adding one layer of detail at a time, until every part is trivial to implement.
| Level | What it contains |
|---|---|
| Level 0 | The whole problem, as one box |
| Level 1 | Its major sub-systems |
| Level 2 | The components within each sub-system |
| Level 3+ | Concrete procedures and functions |
It is the opposite of bottom-up design, where you build small components first and assemble them into a system. Top-down keeps the big picture in view throughout, which has real advantages:
| Advantage of top-down | Detail |
|---|---|
| Clear overview | You never lose sight of how a part serves the whole |
| Incremental detail | Each refinement step is small and low-risk |
| Team-friendly | Whole sub-trees can be handed to different developers |
| Early testing with stubs | High-level logic can be tested using stubs — placeholder procedures that return fixed values — before the low-level parts exist |
Stepwise refinement can also be written in prose, as a refinement of an algorithm rather than a diagram. Watch "mark a class register" refine from one line into runnable structure:
Level 0: Mark the register for a class
Level 1: - Load the class list
- For each student, record present/absent
- Save the register
Level 2: Load the class list:
open the class file; read each student into a list
Record present/absent:
for each student: display name; read a yes/no; store it
Save the register:
open today's register file; write each student + status; close
Each level is correct on its own and simply has more detail than the one above. By the bottom level the steps map almost line-for-line onto code — which is exactly when refinement stops.
The stub technique deserves a closer look because it is where top-down design pays off in practice. A stub is a placeholder version of a low-level procedure that does nothing real — it might just return a fixed value or print "called load_class_list". With stubs standing in for the not-yet-written parts, you can run and test the high-level logic immediately, confirming that the overall structure and the order of calls are correct before investing effort in the details. For the register example, you could stub load_class_list to return two hard-coded students and stub save_register to do nothing, then check that the marking loop behaves correctly; only once the skeleton works do you replace each stub with its real implementation, one at a time. This test-as-you-build rhythm is one of the strongest arguments for top-down design and is precisely the disciplined approach examiners reward in the NEA development write-up. It also keeps the program in a runnable state throughout, so a bug introduced when fleshing out one procedure is caught immediately rather than emerging from a tangle at the very end.
A structure diagram (hierarchy chart) is the standard visual record of a top-down decomposition. The whole system sits at the top; lines connect each parent module to its child modules; each level down adds detail.
flowchart TB
C["Calculator"]
C --> I["Get input"]
C --> P["Process"]
C --> O["Output result"]
I --> I1["Read number"]
I --> I2["Read operator"]
P --> P1["Add"]
P --> P2["Subtract"]
P --> P3["Multiply"]
O --> O1["Format result"]
O --> O2["Display result"]
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.