SICP
counting change
computer science
programming
recursion

SICP example Counting change, cannot understand

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

The SICP "counting change" example demonstrates recursive problem decomposition, not just coin counting. It computes how many different combinations of coins can make a target amount, using a recursive split: include current coin denomination or skip it. Many learners get stuck because they expect the function to return one best combination rather than the number of valid combinations.

Core Sections

Core recursive idea

At each step with amount n and k coin types:

  • count ways excluding coin k,
  • count ways including coin k at least once,
  • sum the two counts.

Base cases:

  • amount 0 -> one valid way,
  • amount < 0 -> no way,
  • no coin types left -> no way.

Python equivalent of SICP pattern

python
1def first_denomination(k):
2    coins = [1, 5, 10, 25, 50]
3    return coins[k - 1]
4
5def cc(amount, kinds):
6    if amount == 0:
7        return 1
8    if amount < 0 or kinds == 0:
9        return 0
10    return cc(amount, kinds - 1) + cc(amount - first_denomination(kinds), kinds)
11
12print(cc(100, 5))

This mirrors the SICP structure.

Why it works

The two recursive branches partition solution space without overlap:

  • branch A never uses coin k,
  • branch B uses coin k at least once.

No combination belongs to both branches.

Time complexity and optimization

Naive recursion has repeated subproblems. Memoization or dynamic programming improves efficiency.

python
1from functools import lru_cache
2
3@lru_cache(None)
4def cc_memo(amount, kinds):
5    ...

Pedagogical purpose

SICP uses this example to teach abstraction and recursive process modeling, not production-grade performance.

Common Pitfalls

  • Interpreting output as minimum coins instead of number of combinations.
  • Missing base cases and causing infinite recursion or wrong counts.
  • Assuming branches overlap and double-count solutions.
  • Getting confused by denomination indexing direction.
  • Ignoring repeated-subproblem cost in naive recursion.

Implementation Playbook

When studying recursive combinatorics problems, write the decision partition in plain language before coding. This reduces confusion between "count ways" and "find best" formulations. Add tiny test cases (amount=0, small values with known answers) to verify base-case behavior early.

After conceptual correctness, instrument function call counts to observe growth and motivate memoization. Translating the same recurrence to bottom-up DP is a useful next step because it reinforces that recursion and DP solve identical subproblems with different execution order. Keep both implementations for learning and benchmark comparisons.

text
11. Write decision split in plain language
22. Validate base cases with tiny inputs
33. Compare recursive output with known counts
44. Add memoization and measure speedup
55. Implement DP equivalent for reinforcement
66. Document what metric is being counted

Operational Readiness

Converting a technically correct implementation into a reliable production behavior requires explicit operational guardrails. Begin by defining success criteria in measurable terms: expected output shape, acceptable latency range, and acceptable failure rate under normal load. Then build a minimal verification harness that exercises the same code path with deterministic fixtures so behavioral drift is detected early when dependencies or runtime versions change. This harness should run quickly enough to execute on every change and should fail loudly when assumptions break.

Next, establish observability that captures both correctness and health. Structured logs should include correlation identifiers, key decision branches, and error classifications. Metrics should track throughput, latency percentiles, and error categories relevant to this workflow. If external integrations are involved, include dependency status and timeout counters so incident triage can isolate whether failures originate locally or downstream. Avoid relying on manual spot checks because intermittent regressions are often timing-sensitive and disappear outside repeatable test conditions.

Finally, define a controlled rollout and rollback process. Deploy incrementally, compare live metrics against baseline, and keep rollback criteria explicit before release starts. Store configuration assumptions in a short runbook so future maintainers can reproduce intended behavior quickly. A disciplined rollout model dramatically reduces recovery time when unexpected behavior appears after infrastructure, network, or platform changes.

text
11. Define measurable success and failure thresholds
22. Run deterministic fixture-based smoke checks
33. Capture structured logs and core metrics
44. Validate downstream dependency behavior
55. Roll out incrementally with explicit rollback triggers
66. Keep runbook assumptions current

Summary

SICP counting-change counts combinations through recursive decomposition of include/exclude decisions. The main conceptual win is understanding subproblem structure; performance optimization comes later with memoization or DP.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.