Skip to content
intermediate

Count and Say

The Count and Say solution is a repeated state transition: start with "1", scan the current string into maximal consecutive runs, and emit each run as…

Published 2026-09-07Updated 2026-09-1210 min read
A stylish workspace featuring a laptop, plant, and smartphone on a desk.
A stylish workspace featuring a laptop, plant, and smartphone on a desk. Photo by Lisa Fotios on Pexels.
Problem

Count and Say

Difficulty: MediumAcceptance rate: 63.7%

Return the nth string in the count-and-say sequence, where the first string is "1" and each subsequent string is the run-length encoding of the preceding string.

String

Constraints

  • 1 <= n <= 30

Important details

  • Run-length encoding represents each maximal consecutive group by its length followed by the repeated digit.
  • The input n is positive.

The Count and Say solution is a repeated state transition: start with "1", scan the current string into maximal consecutive runs, and emit each run as count + digit.

The sequence begins:

"1"
"11"
"21"
"1211"
"111221"
"312211"

For example, "1211" becomes:

  • one 1"11"
  • one 2"12"
  • two 1s → "21"

Concatenating those fragments produces "111221".

The key interview move is to stop treating “say the digits” as vague string manipulation. Each next term is the run-length encoding of the previous term. Once that transition is explicit, the problem becomes a controlled scan with a small amount of state.

Turn the recurrence into a transition

The sequence is 1-indexed:

term 1 = "1"
term n = run-length encoding of term n - 1

The requested result is a string, not a numeric value. Even though every character is a digit, the sequence describes representations. Converting a term to an integer would discard the structure we need to scan.

There are two layers of state:

  1. Outer state: the complete current term.
  2. Inner state: the run currently being scanned inside that term.

Starting with "1", apply the same transformation exactly n - 1 times.

That count matters. If n == 1, the starting value is already the answer. Applying the transformation n times would return the next term instead.

A useful way to express the algorithm is:

current = "1"

repeat n - 1 times:
    current = next_term(current)

return current

The real work is therefore concentrated in next_term. It must partition the input into maximal adjacent groups and describe those groups from left to right.

Recognize the scan, not a frequency count

The most tempting wrong approach is to count how often each digit appears in the entire string. That loses the information the sequence depends on: order and adjacency.

Consider:

"1211"

The digit 1 appears three times overall, but the string contains three runs:

"1"  "2"  "11"

The first 1 and the final 11 must be described separately because they are separated by a 2.

A frequency map would see “three 1s and one 2.” The sequence needs “one 1, one 2, two 1s.” Those are different outputs.

For each run, the scan has four obligations:

  1. Identify the run's digit.
  2. Advance until the digit changes or the string ends.
  3. Emit the run exactly once.
  4. Continue from the first unprocessed character.

Use two indices:

  • i: start of the current run.
  • j: first position after that run.

The central invariant is:

Before processing the run starting at i, every character before i has been consumed and encoded exactly once. The interval from i up to—but not including—j is the current maximal run.

This invariant gives the loop a clear job. It must move j to the run boundary, emit j - i and current[i], then set i = j.

A scan that emits before the run ends can fragment a group. A scan that searches globally can merge nonadjacent equal digits. The boundary is part of the answer.

Build one-step run-length encoding

Define a helper conceptually:

next_term(current) -> encoded version of current

The two-index version follows directly from the obligations:

i = 0
while i < len(current):
    digit = current[i]
    j = i

    while j < len(current) and current[j] == digit:
        j += 1

    count = j - i
    append str(count) + digit
    i = j

The inner loop advances j through one maximal run. Because i then jumps to j, no character is revisited by the outer loop.

Dry run: encoding "1211"

A left-to-right trace of the string 1211 divided into runs 1, 2, and 11; each run is labeled with its count and digit, producing fragments 11, 12, and 21 that join to form 111221.
The scan jumps from each run start to its first differing character, emits one fragment, and continues at the next unprocessed position.

Start with:

current = "1211"
i = 0

First run

current[i] is 1.

Advance j while the digit remains 1:

i = 0
j = 1

The run is current[0:1], which contains one 1.

Emit:

str(1) + "1" = "11"

Set i = 1.

Second run

current[1] is 2.

The next character is 1, so the run ends immediately:

i = 1
j = 2

Emit:

str(1) + "2" = "12"

Set i = 2.

Third run

current[2] is 1.

The next character is also 1, so advance to the end:

i = 2
j = 4

The run length is 4 - 2 = 2.

Emit:

str(2) + "1" = "21"

Set i = 4. The scan is complete.

The fragments are:

"11", "12", "21"

Joining them gives:

"111221"

The count must be converted with str(count) before concatenation. The output is text all the way through; it is not an arithmetic expression.

Prove the scan covers every run

The implementation is short, but the correctness argument should be visible.

Termination

For every run, the inner loop advances j by at least one position because every run is nonempty. After emitting the run, the outer loop sets i = j, so i also moves forward.

The outer recurrence applies the transformation only n - 1 times. Since n is positive and bounded by 30, both loops terminate.

Coverage and non-overlap

At any point, all positions before i have already been encoded. The inner loop advances j until either:

  • j reaches the end of the string, or
  • current[j] differs from the run digit.

Therefore, the interval [i, j) is a maximal run. It is nonempty, and the next scan begins exactly at j.

The runs are adjacent, non-overlapping, and cover the entire input string. No character is skipped or processed twice.

Correct emission

The run digit is current[i].

The run length is:

j - i

So the emitted fragment:

str(j - i) + current[i]

matches the definition of run-length encoding.

Because fragments are appended in left-to-right order, their order matches the order of runs in the previous term.

Induction over sequence terms

The base term is "1", which is correct for term 1.

Assume current is term k. The scan partitions it into every maximal consecutive run and emits the correct count-plus-digit fragment for each run. Therefore, next_term(current) is term k + 1.

Applying the transition n - 1 times produces term n.

That is the important distinction between code that happens to match a few examples and code whose state transition is justified.

Write the Python solution around visible state

For a Count and Say Python solution, I prefer the explicit boundary scan over a compressed loop. The variables expose the obligations:

  • i marks the first unprocessed character.
  • j finds the end of the current run.
  • digit stores the run's value.
  • parts stores one output fragment per completed run.
def countAndSay(n: int) -> str:
    current = "1"

    def next_term(value: str) -> str:
        parts = []
        i = 0

        while i < len(value):
            digit = value[i]
            j = i

            while j < len(value) and value[j] == digit:
                j += 1

            parts.append(str(j - i) + digit)
            i = j

        return "".join(parts)

    for _ in range(n - 1):
        current = next_term(current)

    return current

The outer loop starts from a known valid term and performs exactly the remaining transitions. The helper does one complete run-length encoding pass.

Building a list of fragments and joining it at the end makes the output construction explicit. Each fragment corresponds to one consumed run. It also avoids making repeated string concatenation the central operation inside the scan.

Keep every term as a string. The count is temporarily an integer because subtraction gives us a length, but the final fragment converts it back to text. The sequence is about representation, not numeric magnitude.

I would choose this iterative implementation in an interview even though a recursive version is possible. Iteration mirrors the recurrence directly, keeps the number of transformations visible, and avoids adding call-stack state to a problem whose natural state is already sequential.

Complexity, growth, and edge-case checks

Let L_k be the length of term k.

One transformation scans all L_k characters and constructs the next term. Its time complexity is:

O(L_k)

To generate term n, the total work is:

O(L_1 + L_2 + ... + L_n)

If L is the maximum generated term length, this is often summarized as:

O(nL)

The current term and the next term must coexist during a transformation. The fragment list also holds the next output pieces, whose total size is proportional to the next term. Thus the working space is:

O(L)

where L is the maximum generated length among the terms being held.

Do not assume that every run count fits in one character. The output format is str(count) + digit, so a count may contribute multiple characters. The algorithm already handles that correctly because it converts the full integer count to text.

For the stated range 1 <= n <= 30, repeated linear scans are the intended approach. Hashing, dynamic programming, and global frequency counting do not solve the actual bottleneck or preserve the required adjacency information.

Check these cases before trusting the implementation:

CaseWhat it testsExpected result
n = 1Base case and zero transitions"1"
n = 2A single one-character run"11"
n = 4Adjacent runs with different digits"1211"
Input "1211" to next_termSeparate runs and a final repeated run"111221"
A run at the end of the stringCorrect final-run handlingThe inner loop stops at len(value)

Common failure modes are predictable:

  • Performing n transformations: returns term n + 1.
  • Forgetting the final run: often caused by emitting only when the next digit changes, without a final boundary check.
  • Using a frequency map: merges equal digits that are not adjacent.
  • Emitting one character at a time: fragments a maximal run instead of describing it once.
  • Appending an integer directly: mixes numeric and string representations instead of producing text.
  • Parsing the whole term as an integer: treats a representation sequence as arithmetic data and can lose leading-structure assumptions.

The best debugging technique is simple: print the current term before each transition, then print the run fragments produced by the helper. If a result is wrong, you can see whether the defect is in the outer iteration count or in one local run boundary.

Reuse the pattern: summarize, then transform

Count and Say is a compact example of a broader state-tracking pattern:

When the next object is a deterministic description of the previous object, first derive the one-step transformation. Then identify the smallest local state that lets you emit each description exactly once.

Here, the local summary is not a global count. It is the current run boundary:

  • which digit is being described,
  • where the run started,
  • where it ends,
  • how many characters it contains.

The implementation check is equally reusable:

Every output fragment must correspond to one maximal, already-consumed input region. The scan must neither revisit nor skip a character.

In an interview, write one transition by hand before writing the full loop. Mark the state before and after each emitted fragment:

input region -> output fragment
"1"           -> "11"
"2"           -> "12"
"11"          -> "21"

Then encode that verified transition and repeat it n - 1 times.

The durable lesson is not merely “use two pointers.” It is sharper: read a sequence definition as a state machine. Name the current state, derive one deterministic transition, state the invariant that makes the transition safe, and test the boundaries where state changes. Clear the noise. Find the run. Emit it once. Then move on.

References

  1. Count and Sayleetcode.com
8sources checked
8source domains
5searches run

Research updated Sep 7, 2026

Related sites

Strengthen the language foundations behind the solution

Use LearnPyFast and LearnJSFast when you want to reinforce the language mechanics that support interview implementations.

Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast
JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast

Keep grinding

Related coding interview problems

Continue with nearby problems that reuse the same data-structure, invariant, or optimization pattern.

Lush green pine tree with a vibrant blue sky background, perfect for nature-themed projects.
beginner
11 min read

Add Binary

You receive two binary strings, a and b, and must return their sum as another binary string. The inputs contain only '0' and '1', have lengths from 1 to…

View solution
A person working on a laptop with a red notebook and glasses on a white table.
intermediate
10 min read

Add Two Numbers

The lists already expose digits in the order addition needs. Scan both lists together, track one carry, and keep going until there is no digit or carry…

View solution