Back to Google questions
CodingSoftware Engineer

License Plate — Map Sequential Number to Plate String

Role: Software Engineer

Frequency: Reported (technical round; multi-part with follow-ups)


Problem Overview

The DMV issues plates in a fixed pattern — originally AA000, AA001, …, ZZ999 (two letters followed by three digits). Given a sequential index n, return the plate string that corresponds to it:

  • 0 → AA000
  • 1 → AA001
  • 2 → AA002
  • …
  • 999 → AA999
  • 1000 → AB000
  • …

You're given two helpers you may use:

  • alpha(n) — returns n in base-26 as uppercase letters (0 → "A", 1 → "B", …, 25 → "Z", 26 → "BA").
  • pad(width, padding, value) — left-pads value with padding to width characters. pad(8, '0', '107') → '00000107'.

Basic Solution (Fixed AA000 Format)

python
def num_to_plate(n: int) -> str:
    # digits portion: last 3 chars, zero-padded
    digits = pad(3, '0', str(n % 1000))

    # letter portion: advance every 1000 sequential numbers
    letters = alpha(n // 1000)
    letters = pad(2, 'A', letters)  # pad to 2 chars with 'A'

    return letters + digits

The key insight is that the letters and digits are independent counters advancing at different rates: the digit suffix wraps every 1000, which bumps the letter prefix. alpha(n // 1000) gives the letter string; pad to exactly two letters with leading A handles the single-letter case (alpha(0) → "A" becomes "AA").


Follow-up 1 — What If n Overflows the Plate Format?

AA000–ZZ999 has 26² · 10³ = 676,000 plates. Beyond that, the original format is exhausted. Options:

  • Return None / throw. Simple; leaves policy to the caller.
  • Grow the prefix. AAA000 — but that changes the format.
  • Increment the letter prefix. BA000, etc. — requires generalizing alpha to unbounded length.

In interviews, state your chosen policy up-front and move on. None is fine; the interviewer is checking that you noticed the overflow.


Follow-up 2 — General Format: m Letters + d Digits

The plate format becomes configurable. The DMV issues plates in lexicographic cycles:

PatternLettersDigitsRange example
00000050 → 00000, …, 99999
A0000140 → A0000, …, Z9999
AA000230 → AA000, …, ZZ999
AAA00320 → AAA00, …, ZZZ99
AAAA0410 → AAAA0, …, ZZZZ9
AAAAA500 → AAAAA, …, ZZZZZ

The system issues plates in order (m=0, d=5), then (m=1, d=4), then (m=2, d=3), etc. Each bucket has 26^m · 10^d plates.

Solution

python
def solution(n: int) -> str | None:
    # Bucket definitions in issuance order
    configs = [(0, 5), (1, 4), (2, 3), (3, 2), (4, 1), (5, 0)]
    counts = [(26 ** m) * (10 ** d) for m, d in configs]

    # Step 1: find which bucket n falls into
    total = 0
    for (m, d), c in zip(configs, counts):
        if n < total + c:
            offset = n - total
            break
        total += c
    else:
        return None  # beyond the last bucket — exhausted

    # Step 2: within the bucket, split offset into letter and digit portions
    digit_part = pad(d, '0', str(offset % (10 ** d))) if d > 0 else ''
    letter_part = pad(m, 'A', alpha(offset // (10 ** d))) if m > 0 else ''
    return letter_part + digit_part

Why divide by 10^d? Within a bucket, the digit portion is the fast-moving counter (wraps 10^d times) and the letter portion advances every 10^d plates. Same pattern as the basic version, just generalized.


Key Design Considerations

  • Two counters, two rates. The letters and digits are fundamentally two bases running in parallel: letters at rate 10^d, digits at rate 1. This factoring is what keeps the code short.
  • Bucket boundaries are precomputed. Don't loop n times to find the bucket. Compute the cumulative count of each format and binary-search or linear-scan in O(6) = O(1).
  • Edge cases for each bucket: m = 0 means no letter prefix (pure digit plate); d = 0 means no digit suffix (pure letter plate). Both break if you don't guard with if d > 0 / if m > 0.
  • pad with 'A' vs '0'. Letter portions pad with 'A' because alpha(0) = "A" naturally — you want "A" → "AA", not "A" → "0A".

Follow-up Questions

  1. Skipping letters. Real DMVs skip I, O, Q to avoid confusion with 1 and 0. How does that change alpha and the bucket counts?
  2. Reverse lookup: given a plate string, return the sequential n. How does this decompose?
  3. Custom alphabet per position. What if letter positions 1 and 2 draw from different subsets (e.g., first letter can't be a vowel)?