Coding Interview Patterns Module 24 – Dynamic programming: foundations
Counting ways with modulo arithmetic
Count the ways to read a run of digits as letters 1 to 26, modulo 1,000,000,007: the problem, the brute force, and what makes the counts grow so fast.
What you will learn
- Count the readings of a digit string with a table over positions, modulo 10^9 + 7
- Handle zeros and two-digit letters so that every reading is counted once
- Avoid overflow in Java, C++, Go and JavaScript when counts are added and multiplied
- Reproduce a lost-precision bug in JavaScript and fix it
- Extend the count to digits that may stand for any value
Before you start
What you’ll learn · Before you start
What you will learn
- Count the readings of a digit string with a table over positions, modulo 10^9 + 7
- Handle zeros and two-digit letters so that every reading is counted once
- Avoid overflow in Java, C++, Go and JavaScript when counts are added and multiplied
- Reproduce a lost-precision bug in JavaScript and fix it
- Extend the count to digits that may stand for any value
Before you start
On this page
Counting DP adds up the number of ways to finish instead of keeping the best one, and the counts grow so fast that problems ask for them modulo a prime, usually 1,000,000,007. The table is the easy part. The care goes into three details: counting each way exactly once, keeping every intermediate value inside the integer type of the language, and choosing the base case of an empty rest, which counts as one way. The problem here is a classic of this kind, told in an original form: in how many ways can a run of digits be read as letters?
The problem: a pager that runs letters together
The problem
An old pager service sends text as letter numbers, A = 1 to Z = 26, written one after another with nothing between
them. A letter number never starts with 0, so 05 is not a letter, but 10 and 20 are. Given the digits received,
return the number of different letter strings they could stand for, modulo 1,000,000,007. Limits:
1 ≤ len(digits) ≤ 100,000, each character a digit from 0 to 9.
2611has 4 readings: BFAA, BFK, ZAA and ZK, from 2-6-1-1, 2-6-11, 26-1-1 and 26-11.1020has 1, JT (10-20), because each 0 must be the second digit of its letter.301has none: neither 30 nor a letter that starts with 0 exists.- Ten 1s have 89 readings.
Every reading of 2611, and the rest that is read twice
Text description of the diagram
The tree starts at the whole message, 2611. Each arrow reads one letter from the front, of one or two digits, and leads to the part still to read.
- Reading 2 as B leaves 611; reading 6 as F then leaves 11.
- Reading 26 as Z leaves 11 straight away.
The rest 11 therefore appears twice, once after B and F and once after Z, and both boxes have a double border. From each of them there are two ways on: 11 read as K, or 1 and 1 read as A and A. That gives the four readings at the ends of the tree: ZK, ZAA, BFK and BFAA.
The brute force works out the readings of 11 once for each box. A longer message repeats such rests far more often, which is what the table of the lesson avoids.
The brute force, and why it fails
Read the first letter as one digit or as two, and count every way on from there:
import math
def readings_brute(digits):
"""Reads the first letter as one digit or as two, and every way on from there; counts the calls it makes."""
calls = 0
def read_from(i):
nonlocal calls
calls += 1
if i == len(digits):
return 1 # the whole message has been read: one reading
found = 0
if digits[i] != "0":
found += read_from(i + 1) # a letter of one digit, 1 to 9
if i + 1 < len(digits) and 10 <= int(digits[i : i + 2]) <= 26:
found += read_from(i + 2) # a letter of two digits, 10 to 26
return found
return read_from(0), calls
for digits in ("2611", "1020", "301", "1111111111"):
count, calls = readings_brute(digits)
print(f"{digits}: {count} {'reading' if count == 1 else 'readings'} ({calls} calls)")
for n in (10, 20, 25):
count, calls = readings_brute("1" * n)
print(f"{n} ones: {count:,} readings, {calls:,} calls")
shorter, current = 0, 1 # exact counts for k - 1 and k ones, starting at k = 0: the empty message has one reading
for _ in range(100_000):
shorter, current = current, shorter + current
print(f"100,000 ones: an exact count with {int(math.log10(current)) + 1:,} digits") Output
2611: 4 readings (10 calls) 1020: 1 reading (5 calls) 301: 0 readings (2 calls) 1111111111: 89 readings (232 calls) 10 ones: 89 readings, 232 calls 20 ones: 10,946 readings, 28,656 calls 25 ones: 121,393 readings, 317,810 calls 100,000 ones: an exact count with 20,899 digits
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 readings_brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The counts are right and so is the cost: about 2.6 calls for every reading found, and the readings of a run of 1s grow by a factor of about 1.6 per digit, 121,393 for 25 ones. For 100,000 ones the exact count has 20,899 digits, so neither listing the readings nor counting them one call at a time can finish.
What to notice
- The readings of the rest depend only on where the rest starts. In the tree above, the rest 11 is reached twice and counted twice; a table would count it once.
- A 0 can only be the second digit of 10 or 20, so zeros decide which cuts exist at all.
- The counts grow like Fibonacci numbers, which is why the answer is asked modulo a prime, and why every language has to keep its intermediate values inside its integer types.
The table over positions, with zeros handled Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why every reading is counted once
- Reducing after every addition, and why it gives the same residue
- Products of counts: a transmission of several messages
- The JavaScript precision bug, recorded, and the BigInt fix
- The code in five languages
- Smudged digits: the letters a pattern can be
- Five bugs and the smallest inputs that break them
- Exercise
- Count the readings of a message with smudged digits
- Check yourself
- A practice set with sample answers
- Interview questions
- A teammate's decoder counts the readings of a digit string with a table over positions and passes every test that has no zeros. Which messages with zeros is it likely to get wrong, what are the exact rules for a 0, and how do you prove that a fixed table counts each reading once?
- A transmission is several digit messages, and its readings are the product of the readings of each, modulo 1,000,000,007. Where exactly must the remainders be taken, and which values need 64 bits in Java, C++, Go and JavaScript? Show one bug that a test with short messages would never catch.
- Digits arrive one at a time over a slow link, millions of them, and after each digit you must report the number of readings of everything received so far, modulo 1,000,000,007, using constant memory. Which way does the table run now, what do you keep, and what does each digit cost?
- Key takeaways
8 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 21 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- ECMAScript 2026 Language Specification: Number.MAX_SAFE_INTEGER (Ecma International)
- ECMAScript 2026 Language Specification: The BigInt type (Ecma International)
- The Java Language Specification, Java SE 25: 15.17.1 Multiplication operator * (Oracle)
- The Java Language Specification, Java SE 25: 4.2.1 Integral types and values (Oracle)
- Math.multiplyExact (Java SE 25 API) (Oracle)
- The Go Programming Language Specification: Integer overflow (The Go Authors)
- Arithmetic operators: overflows (cppreference.com) (cppreference.com)
- NIST Dictionary of Algorithms and Data Structures: Fibonacci number (National Institute of Standards and Technology (NIST))
- Numeric types: int, float, complex (The Python Standard Library) (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress