Mustaque Nadim Academy
Recursion & Math

Number Basics

Counting digits, summing naturals, checking even or odd — the small number tricks that show up inside bigger algorithms.

The problem

You are formatting a number for a UI and need to know how many digits it has, so you can pad it. Your first instinct is to convert it to a string and read .length. It works — until you profile a hot loop that does this a million times and watch the string allocations pile up in the garbage collector.

There is a cheaper way that never touches a string. It comes from a single observation about how numbers are built, and the same observation powers reversing a number, summing its digits, and half a dozen tricks buried inside larger algorithms.

A first attempt

The string approach reads naturally:

def count_digits(n):
    return len(str(abs(n)))

It is O(d) where d is the number of digits, which is fine on paper. But it allocates a whole string object just to measure its length, and in a tight numeric loop those allocations dominate. You are doing text work to answer an arithmetic question. The number itself already knows how many digits it has — you just have to ask it in the language of arithmetic.

The insight

In base 10, dividing an integer by 10 (integer division) chops off its last digit, and % 10 reads that last digit. So n // 10 is "the same number with one fewer digit." That is a self-similar shrink — exactly what recursion feeds on.

Counting digits becomes: one digit, plus however many digits are left after you remove it.

  • Base case: a single-digit number (n < 10) has 1 digit.
  • Recursive case: count_digits(n) = 1 + count_digits(n // 10).

The same // 10 and % 10 pair reverses a number, sums its digits, or extracts them one by one — no strings anywhere.

How it works

Normalize the sign

Digits are about magnitude, so work with abs(n). Doing it once up front keeps the recursion clean and handles negative inputs correctly.

Read the base case

If n < 10 the number is a single digit, so the answer is 1. This also handles 0, which we treat as having one digit.

Chop and recurse

n // 10 removes the last digit. Add 1 for the digit you removed and recurse on the shorter number:

count_digits(4092)
= 1 + count_digits(409)
= 1 + 1 + count_digits(40)
= 1 + 1 + 1 + count_digits(4)
= 1 + 1 + 1 + 1        <- base case, 4 < 10
= 4

Reuse the pattern

Swap the combine step and you get relatives: n % 10 + sum_digits(n // 10) sums the digits; building reversed * 10 + n % 10 reverses the number. One shrink, many uses.

The code

def count_digits(n):
    n = abs(n)
    if n < 10:                     # base case: single digit
        return 1
    return 1 + count_digits(n // 10)


def sum_digits(n):
    n = abs(n)
    if n < 10:
        return n
    return n % 10 + sum_digits(n // 10)


print(count_digits(4092))  # 4
print(sum_digits(4092))    # 15
function countDigits(n: number): number {
  n = Math.abs(n);
  if (n < 10) return 1; // base case: single digit
  return 1 + countDigits(Math.floor(n / 10));
}

function sumDigits(n: number): number {
  n = Math.abs(n);
  if (n < 10) return n;
  return (n % 10) + sumDigits(Math.floor(n / 10));
}

console.log(countDigits(4092)); // 4
console.log(sumDigits(4092)); // 15
static int countDigits(int n) {
    n = Math.abs(n);
    if (n < 10) return 1;                 // base case
    return 1 + countDigits(n / 10);
}

static int sumDigits(int n) {
    n = Math.abs(n);
    if (n < 10) return n;
    return n % 10 + sumDigits(n / 10);
}

// countDigits(4092) -> 4,  sumDigits(4092) -> 15
#include <stdlib.h>

int count_digits(int n) {
    n = abs(n);
    if (n < 10) return 1;                 /* base case */
    return 1 + count_digits(n / 10);
}

int sum_digits(int n) {
    n = abs(n);
    if (n < 10) return n;
    return n % 10 + sum_digits(n / 10);
}

/* count_digits(4092) -> 4,  sum_digits(4092) -> 15 */
#include <cstdlib>

int countDigits(int n) {
    n = std::abs(n);
    if (n < 10) return 1;                 // base case
    return 1 + countDigits(n / 10);
}

int sumDigits(int n) {
    n = std::abs(n);
    if (n < 10) return n;
    return n % 10 + sumDigits(n / 10);
}

// countDigits(4092) -> 4,  sumDigits(4092) -> 15

Complexity

OperationTimeSpaceNote
Count digitsO(log n)O(log n)Digit count is ⌊log₁₀ n⌋ + 1, so it recurses that many times.
Sum of digitsO(log n)O(log n)One recursive frame per digit.
Even / oddO(1)O(1)A single n % 2 (or n & 1) — no recursion needed.

The recursion depth is the digit count, which is O(log₁₀ n) in the value of n — logarithmic, not linear, in the number itself.

When to use it

Prefer arithmetic over string conversion in hot paths

The // 10 and % 10 pair does digit work with pure arithmetic and zero allocation, which matters inside tight loops and on memory-constrained systems. For a one-off in readable app code, len(str(n)) is perfectly fine — reach for the arithmetic version when the operation is on a hot path or when strings are unavailable (embedded C, for example).

Practice

Recap

  • Integer division by 10 chops the last digit; % 10 reads it — together they shrink a number recursively.
  • Counting digits, summing digits, and reversing all reuse that one pair with no string conversion.
  • These operations run in O(log n) — the digit count — not O(n).

How is this guide?

Last updated on

On this page