Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25.0.4.1, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 9 of 35 minutes free
Free: The problem: a pager that runs letters together · The brute force, and why it fails · What to notice. With a pass: the other 12 sections and 3 interview answers, about 26 minutes.

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.

  • 2611 has 4 readings: BFAA, BFK, ZAA and ZK, from 2-6-1-1, 2-6-11, 26-1-1 and 26-11.
  • 1020 has 1, JT (10-20), because each 0 must be the second digit of its letter.
  • 301 has none: neither 30 nor a letter that starts with 0 exists.
  • Ten 1s have 89 readings.
The cuts of the message 2611: a tree whose four ends are the readings ZK, ZAA, BFK and BFAA, with the rest 11 reached twice.2611611111111ZKZAABFKBFAA2 = B26 = Z6 = F11 = K1 = A1 = A11 = K1 = A1 = A

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.

  1. Reading 2 as B leaves 611; reading 6 as F then leaves 11.
  2. 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:

Every reading tried, with the calls counted Python · brute/readings_brute.py
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

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.

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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.