What Problem Decomposition Means
Problem decomposition is the skill of turning one large, vague goal into a set of smaller, well-defined subproblems that can be solved independently and then combined. In programming logic, this is how you reduce complexity: each subproblem becomes a unit you can reason about, test, and improve without holding the entire system in your head.
A good decomposition produces subproblems that are:
- Clear: each has a specific input and output.
- Independent: minimal overlap with other subproblems.
- Composable: outputs of some become inputs to others.
- Testable: you can verify each part in isolation.
A Step-by-Step Workflow for Decomposing Problems
Step 1: Identify the Main Objective (the “North Star”)
Write the objective as a single sentence that describes what “done” means. Avoid implementation details. Include success criteria when possible.
- Template: “Build a system that
[does X]for[who/what]under[constraints].” - Example: “Compute a restaurant bill total including tax and tip, given item prices, tax rate, and tip percentage.”
Step 2: List Major Steps (top-level subproblems)
Ask: “What are the big phases that must happen for the objective to be achieved?” Keep this list short (often 3–7 items). These are not yet functions; they are major responsibilities.
Common categories that often appear:
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
- Input acquisition (read, parse, validate)
- Core computation (rules, transformations)
- Output formatting (display, store, transmit)
- Error handling and edge cases
Step 3: Split Each Major Step into Substeps
For each major step, repeatedly ask: “What smaller actions are required to complete this step?” Stop splitting when a substep is small enough that you can implement it without further planning, and you can describe its inputs/outputs precisely.
A practical stopping rule: if a substep still contains “and” (e.g., “validate and compute and format”), it likely needs further splitting.
Step 4: Assign Responsibilities to Each Subproblem
Turn each subproblem into a responsibility statement with explicit boundaries. This is where you decide what each part owns and what it does not own.
| Subproblem | Responsibility | Inputs | Outputs | Does NOT do |
|---|---|---|---|---|
parseItems | Convert raw item text into numeric prices | raw text | list of numbers | Compute tax/tip |
computeSubtotal | Sum item prices | list of numbers | subtotal | Validate formatting |
computeTax | Compute tax amount from subtotal and rate | subtotal, taxRate | taxAmount | Round final total (unless specified) |
formatReceipt | Produce human-readable output | subtotal, tax, tip, total | formatted string | Any calculations |
This “does NOT do” column is a powerful way to prevent accidental coupling and duplicated logic.
Example: Decomposing a Real Task into Subproblems
Problem Statement
“Given a list of item prices, a tax rate, and a tip percentage, compute and display a receipt with subtotal, tax, tip, and total.”
1) Main Objective
Compute receipt totals correctly and present them clearly.
2) Major Steps
- Read and validate inputs
- Compute monetary values
- Format and display receipt
3) Substeps
- Read and validate inputs
- Get item prices
- Ensure each price is a valid non-negative number
- Get tax rate and tip percentage
- Ensure rates are valid (e.g., 0–1 or 0–100 depending on chosen convention)
- Compute monetary values
- Compute subtotal
- Compute tax amount
- Compute tip amount (decide: tip on subtotal or subtotal+tax; make it explicit)
- Compute total
- Apply rounding rules (define when rounding occurs)
- Format and display receipt
- Format currency values
- Assemble receipt lines
- Output receipt
4) Assign Responsibilities (boundary decisions)
Boundary decisions are choices that affect correctness and maintainability. For example:
- Where does rounding happen? If each computation rounds, totals may differ from rounding only at the end. Choose one rule and assign it to a single subproblem (e.g.,
applyRoundingRules). - Who decides tip base? Put the rule in one place (e.g.,
computeTip(subtotal, tipRate, tipBaseRule)).
Example Decomposition Tree
A decomposition tree shows the hierarchy from objective to subproblems. One node can be implemented by combining its children.
ComputeReceiptAndDisplayReceipt (objective) ├─ InputHandling │ ├─ ReadItemPrices │ ├─ ValidatePrices │ ├─ ReadTaxRate │ ├─ ReadTipRate │ └─ ValidateRates ├─ Calculations │ ├─ ComputeSubtotal │ ├─ ComputeTax │ ├─ ComputeTip │ ├─ ComputeTotal │ └─ ApplyRoundingRules └─ Output ├─ FormatCurrency ├─ BuildReceiptLines └─ PrintReceiptNotice how each leaf is small enough to implement and test independently.
How to Combine Subproblems: Interfaces and Data Flow
Decomposition is not only splitting; it is also defining how parts connect. A simple way is to define a shared data structure (or a set of values) that flows through the pipeline.
Example of a minimal data flow plan:
prices(list of numbers) →subtotalsubtotal+taxRate→taxAmountsubtotal+tipRate(+ rule) →tipAmountsubtotal+taxAmount+tipAmount→total- All computed values → formatted receipt string
When you can draw the data flow, you can usually implement the program cleanly.
Checklist: Spotting Missing Subproblems
- Inputs: Have you handled all required inputs? Are defaults needed?
- Validation: What can be invalid (empty list, negative numbers, non-numeric input, out-of-range rates)? Who rejects it?
- Edge cases: Zero items, zero rates, very large values, precision/rounding issues.
- Rules: Are business rules explicit (tip base, rounding policy, tax applicability)?
- Output: Is formatting a separate responsibility from computation?
- Error paths: What happens when validation fails? Is there a subproblem for error reporting?
- Duplication: Are two subproblems computing the same thing? If yes, extract a shared subproblem.
- State ownership: Who owns shared data? Is there a single source of truth?
- Testability: Can each subproblem be tested with simple inputs and expected outputs?
- Dependencies: Do subproblems depend on each other in a clear order? Any cycles indicate unclear boundaries.
Exercises: Practice Decomposition and Boundary Justification
Exercise 1 (Small): Temperature Summary
Task: Given a list of daily temperatures, output the minimum, maximum, and average.
Instructions:
- Write the main objective in one sentence.
- List 3–5 major steps.
- Split each major step into substeps until each leaf is implementable.
- Justify your boundaries: Why is “compute average” separate from “compute min/max” (or why not)?
Deliverable: A decomposition tree and a table of responsibilities (inputs/outputs).
Exercise 2 (Medium): Simple Password Validator
Task: Validate a password with rules: minimum length, contains at least one digit, contains at least one uppercase letter, and does not contain spaces. Output which rules failed.
Instructions:
- Decompose into subproblems where each rule check is independent.
- Decide where to aggregate results (single function vs. pipeline of checks).
- Justify boundaries: Why should each rule be its own subproblem? When might you combine them?
Extra challenge: Add a subproblem for generating user-friendly messages without mixing it into validation logic.
Exercise 3 (Medium-Hard): Shopping Cart Total with Discounts
Task: Compute a cart total from items (price, quantity). Apply discounts: “buy 2 get 1 free” on selected items, and a 10% discount if subtotal exceeds a threshold. Then add tax and output a receipt.
Instructions:
- Identify discount rules as separate subproblems.
- Decide the order of operations (discounts before tax, threshold discount based on which subtotal?). Make it explicit.
- Justify boundaries: Where do you place the “order of operations” rule—inside calculation functions or in a coordinator subproblem?
Deliverable: A decomposition tree plus a short paragraph explaining your chosen order-of-operations responsibility.
Exercise 4 (Hard): Command-Line Log Analyzer
Task: Read a text log file where each line contains a timestamp, severity (INFO/WARN/ERROR), and message. Produce a report: count per severity, top 5 most common messages, and the time range covered.
Instructions:
- Decompose into: parsing, validation, aggregation, and reporting.
- Split aggregation into independent subproblems (counts, top messages, time range).
- Justify boundaries: Why should parsing be separate from aggregation? What should the parser output (raw fields vs. structured record)?
Extra challenge: Add a subproblem for handling malformed lines (skip with warning vs. stop with error) and justify the choice.
Exercise 5 (Harder System): Appointment Scheduler (Logic Only)
Task: Design the logic for scheduling appointments with constraints: no overlaps, working hours only, and a minimum 15-minute duration. Inputs are requested start time and duration; output is either a confirmed appointment or a rejection reason.
Instructions:
- Decompose into subproblems that separate time calculations, conflict detection, and rule enforcement.
- Define the data model each subproblem expects (e.g., list of existing appointments, requested appointment).
- Justify boundaries: Where should “rejection reason” be generated—inside each rule check or in a central decision-maker?
Deliverable: A decomposition tree and a checklist of edge cases you think your subproblems cover (e.g., appointment exactly at closing time, duration not multiple of 15, back-to-back appointments).