Free Ebook cover Programming Logic Foundations: Thinking Like a Programmer

Programming Logic Foundations: Thinking Like a Programmer

New course

10 pages

Problem Decomposition and Subproblems in Programming Logic

Capítulo 2

Estimated reading time: 7 minutes

+ Exercise

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 App

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.

SubproblemResponsibilityInputsOutputsDoes NOT do
parseItemsConvert raw item text into numeric pricesraw textlist of numbersCompute tax/tip
computeSubtotalSum item priceslist of numberssubtotalValidate formatting
computeTaxCompute tax amount from subtotal and ratesubtotal, taxRatetaxAmountRound final total (unless specified)
formatReceiptProduce human-readable outputsubtotal, tax, tip, totalformatted stringAny 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  └─ PrintReceipt

Notice 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) → subtotal
  • subtotal + taxRatetaxAmount
  • subtotal + tipRate (+ rule) → tipAmount
  • subtotal + taxAmount + tipAmounttotal
  • 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).

Now answer the exercise about the content:

Which decomposition choice best reduces coupling and improves testability when building a receipt calculator (subtotal, tax, tip, total)?

You are right! Congratulations, now go to the next page

You missed! Try again.

Good decomposition creates clear, independent, composable, testable subproblems with explicit boundaries. Defining inputs/outputs and what each part does NOT do prevents accidental coupling and duplicated logic.

Next chapter

Inputs, Outputs, and Constraints: Specifying the Problem Precisely

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.