The Number Narrator
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Citadel
Problem
A support engineer keeps getting tickets saying the invoicing tool's read-aloud feature garbles amounts, and the fix is to voice each amount, a non-negative integer, as its standard English words. Return that word form, spelling 0 as Zero.
Every number has a story in words.
Example 1
Input:
num = 123
Output:
"One Hundred Twenty Three"
Example 2
Input:
num = 1000000
Output:
"One Million"
Worked solution and explanation
What this problem really is
Strip the costume and this is a base-1000 positional decode: chop the number into three-digit groups, name each group the exact same way, and staple a scale word on. Anyone can print the digits; the skill is surviving the two things that break a naive attempt. First, teens are irregular, so 13 is 'Thirteen', never 'Ten Three', which kills any digit-by-digit scheme and forces a lookup table for 10 through 19. Second, a zero-valued group produces no words at all, so 1000000 must not emit 'Zero Thousand' or leave a doubled space where the empty thousands group used to sit. Miss either and you ship 'One Million' trailed by a stray gap, or 'Ten Three' for a teen.
Break down the requirements
Step 1: Handle zero as a special case
Return 'Zero' immediately, since it does not follow the standard decomposition and would otherwise produce an empty string.
Step 2: Define word mappings for ones, teens, and tens
Store lookup tables for 1-19 (which absorb the irregular teens) and for the multiples of 10 from 20 to 90. Naming by arithmetic is where candidates trip.
Step 3: Process the number in groups of three digits
Peel off billions, millions, thousands, and the remainder in turn. Convert each three-digit group with the same helper, and append its scale label only when the group is non-empty.
Step 4: Convert a three-digit group to words
Handle the hundreds place first, then the last two digits, using the teens table (10-19) or tens plus ones. Collect only non-empty parts so the join stays clean.
The solution
def number_to_words(num):
if num == 0:
return 'Zero'
ones = ['', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven',
'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen',
'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen']
tens = ['', '', 'Twenty', 'Thirty', 'Forty', 'Fifty',
'Sixty', 'Seventy', 'Eighty', 'Ninety']
def convert_chunk(n):
parts = []
if n >= 100:
parts.append(ones[n // 100])
parts.append('Hundred')
n = n % 100
if n >= 20:
parts.append(tens[n // 10])
n = n % 10
if n > 0:
parts.append(ones[n])
return ' '.join(parts)
scales = [(1000000000, 'Billion'), (1000000, 'Million'), (1000, 'Thousand')]
parts = []
for divisor, label in scales:
if num >= divisor:
parts.append(convert_chunk(num // divisor))
parts.append(label)
num = num % divisor
if num > 0:
parts.append(convert_chunk(num))
result = ' '.join(parts)
return resultCommon follow-up questions
- How would you extend this to handle decimals (e.g., 'Three Point One Four')? (Tests splitting on the decimal point and converting each part.)
- What about ordinal numbers (First, Second, Third)? (Tests adding suffix rules for -st, -nd, -rd, -th.)
- How would you handle negative numbers? (Tests prepending 'Negative' and processing the absolute value.)
- What if this ran in a hot loop processing millions of invoices? (Tests caching or pre-computing common values.)