Working with digits
Read, reverse, or sum a number's digits with % 10 and Math.floor(n / 10), without turning it into a string.
O(d)Space O(1) for reversing, O(d) for the digit list3 practice problemsStep-through pictureThe idea
Many small problems are about a number's digits: their sum, their reverse, whether the number reads the same both ways, or a check digit on a card number. You can turn the number into a string, but doing it with arithmetic is just as short and teaches a pattern you'll reuse.
Two operations do all the work. n % 10 is the last digit: 1234 % 10 is 4. Math.floor(n / 10) removes the last digit: 1234 becomes 123. Repeat both until the number is 0, and you see every digit from right to left.
To build a number from digits, go the other way: value = value * 10 + digit shifts the value one place left and puts the digit at the end. Reversing a number is just these two ideas together.
An everyday example
Taking books off a shelf one at a time
Books stand on a shelf, numbered left to right. You take the rightmost book (% 10) and the shelf gets one book shorter (Math.floor(n / 10)). You put each book you take at the right end of a second shelf (* 10 + digit). When the first shelf is empty, the second shelf holds the books in reverse order.
| In the example | In the code |
|---|---|
| The rightmost book | rest % 10 |
| The shelf with that book removed | Math.floor(rest / 10) |
| Adding a book at the right end of the new shelf | reversed * 10 + digit |
| The first shelf is empty | rest === 0 |
From the slow way to working with digits
- Start with the string way:
String(n).split("").reverse().join(""). For38050it gives"05083", andNumberturns that into5083. But for-38050it gives"05083-":Numberturns that intoNaN, andparseIntgives5083with the sign lost. It also builds a string and an array just to move digits around. - Arithmetic can do the same with two operations.
n % 10is the last digit, because every other place is worth a multiple of 10.Math.floor(n / 10)removes that last digit and moves the others one place right. Together they read a number's digits from right to left, one per loop. - Building goes the other way.
value * 10shifts every digit ofvalueone place left and leaves a0at the end; adding a digit fills that place. Sovalue * 10 + digitappends a digit on the right. - Now combine them. Take digits off the right end of
restand append each one to the right end ofreversed: the first digit taken off becomes the first digit of the result, so the order flips. Work onMath.abs(n)and put the sign back at the end. That loop isreverseDigits, and the same reading loop withunshiftisdigitsOf.
How it works
- Work with
rest = Math.abs(n), and deal with the sign at the end. - While
rest > 0, read the last digit:rest % 10. - Use the digit: add it to a sum, push it to a list, or append it to
reversedwithreversed * 10 + digit. - Remove it:
rest = Math.floor(rest / 10). - When
restis0, every digit was seen. Put the sign back if needed. - To list digits left to right, put each new digit at the front (
unshift), and use ado...whileloop so0gives[0].
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function digitsOf(n) {const digits = [];let rest = Math.abs(n);do {digits.unshift(rest % 10);rest = Math.floor(rest / 10);} while (rest > 0);return digits;}function reverseDigits(n) {let rest = Math.abs(n);let reversed = 0;while (rest > 0) {reversed = reversed * 10 + (rest % 10);rest = Math.floor(rest / 10);}return n < 0 ? -reversed : reversed;}
How fast it is
| Time | O(d) |
|---|---|
| Extra memory | O(1) for reversing, O(d) for the digit list |
A number with d digits takes d loop steps. Reversing keeps two numbers; listing the digits keeps d of them.
- Best case
O(1)A one-digit number, or
0, wherereverseDigitsdoesn't loop at all. - Average case
O(d)One step per digit: a number with
ddigits takesdsteps, which is aboutlog₁₀ n. - Worst case
O(d)The longest exact numbers, near
2^53, have 16 digits, so at most 16 steps. Beyond that the input itself isn't stored exactly.
Common mistakes
- Writing
rest / 10withoutMath.floor. The number becomes a decimal like123.4and the loop never reaches exactly0. - Using
%on a negative number and expecting a positive digit.-123 % 10is-3. TakeMath.absfirst. - Forgetting that
0has one digit. Awhileloop returns no digits for it. - Using
parseInt(String(n).split("").reverse().join(""))on negative numbers. The-ends up at the end and is dropped.
When to use it
Look for these clues in a problem:
- The problem talks about the digits of a number: their sum, product, count, or order.
- You must reverse a number or check if it reads the same backward.
- Check digits, like the Luhn check on card numbers, or digit-based IDs.
- The statement forbids converting the number to a string, or asks for
O(1)extra space.
Check yourself
Answer each one in your head first, then open it.
Why does reverseDigits(38050) give 5083 and not 05083?
The trailing zero is the first digit appended, and 0 × 10 + 0 is just 0. A number has no way to store leading zeros.
Why does digitsOf use do...while instead of while?
So the body runs at least once. For 0, a while (rest > 0) loop would never run and return [] instead of [0].
How many loop steps does reverseDigits(1000000) take?
7, one per digit. It returns 1, because the six zeros become leading zeros.
What goes wrong if you write rest = rest / 10 without Math.floor?
rest becomes 380.5, then 38.05, and keeps shrinking without ever being exactly 0 until it underflows: the loop runs 329 times for 38050. reversed grows so fast that it ends as Infinity.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 3 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The loop keeps a promise: the digits taken off rest so far, read in the order they were taken, spell reversed. Any zeros taken first become leading zeros and simply don't show. At the start, nothing has been taken and reversed is 0, so it holds.
Each step keeps it. Writing rest = 10 × q + d with d between 0 and 9 shows that rest % 10 is exactly the last digit d, and Math.floor(rest / 10) is q, the digits before it. reversed * 10 + d puts d after every digit already in reversed, so the order matches the order of removal.
The loop ends because Math.floor(rest / 10) is smaller than rest whenever rest > 0, and it reaches 0 after one step per digit. At that point every digit was taken off from right to left, so reversed holds them from left to right reversed. digitsOf uses unshift instead, putting each new digit in front, which undoes the right-to-left order and lists the digits as written.
Going deeper
3 more ideas
Why % 10 and / 10 give the digits
A number d3 d2 d1 d0 is d3 × 1000 + d2 × 100 + d1 × 10 + d0. Every part but d0 is a multiple of 10, so n % 10 = d0. Dividing by 10 and rounding down drops d0 and moves every other digit one place to the right. This is base conversion with base 10.
Overflow limits
Reversing can make a number bigger, like 1000000009 becoming 9000000001. If a problem limits answers to 32-bit integers (from -2^31 to 2^31 - 1), check the result against those limits and return the required fallback. JavaScript numbers themselves are exact up to 2^53, so the check is easy to do after the loop.
Digital roots and the sum of digits
Summing digits again and again until one digit is left gives the digital root. A shortcut exists: for n > 0 it is 1 + (n - 1) % 9, because a number and its digit sum always have the same remainder when divided by 9. The loop version is just as fast for small numbers and easier to trust.
Work it by hand
Every step in a table, with why
Reverse -38050. It has a trailing zero, a zero in the middle, and a minus sign.
n = -38050
| Step | rest | rest % 10 | reversed | What happens | Why |
|---|---|---|---|---|---|
| 1 | 38050 | - | 0 | rest = Math.abs(n) | The sign is set aside. reversed starts at 0, a number with no digits yet. |
| 2 | 38050 | 0 | 0 | rest = 3805 | The last digit is 0. 0 × 10 + 0 is still 0, so this zero will become an invisible leading zero. |
| 3 | 3805 | 5 | 5 | rest = 380 | 0 × 10 + 5 = 5. The first real digit of the answer. |
| 4 | 380 | 0 | 50 | rest = 38 | 5 × 10 + 0 = 50. A zero in the middle is kept, because it shifts the 5 one place left. |
| 5 | 38 | 8 | 508 | rest = 3 | 50 × 10 + 8 = 508. |
| 6 | 3 | 3 | 5083 | rest = 0 | 508 × 10 + 3 = 5083. Math.floor(3 / 10) is 0. |
| 7 | 0 | - | 5083 | return -5083 | rest is 0, so the loop ends. n was negative, so the sign goes back on. |
It returns -5083, after 5 loop steps for 5 digits. The trailing zero of 38050 turned into a leading zero, which a number can't show. digitsOf(-38050) gives [3, 8, 0, 5, 0], keeping every zero.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Working with digits | O(d) | O(1) | Numbers that fit exactly, when you want no string conversions and full control of signs and zeros. |
| Convert to a string | O(d) | O(d) | Readability matters more than speed, or leading zeros must be kept. Handle the sign yourself. |
BigInt digit loop | O(d²) | O(d) | The number has more than 15 digits. Each / 10n on a long BigInt costs time that grows with its length. |
Extra memory: O(1) for reverseDigits: rest and reversed. digitsOf keeps one entry per digit, O(d).
How the steps grow
- Working with digits, Convert to a string
O(d) BigIntdigit loopO(d²)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Working with digits | 10 | 1,000 | 1 million |
| Convert to a string | 10 | 1,000 | 1 million |
BigInt digit loop | 100 | 1 million | 1 trillion |
Inputs that need care
5 tricky inputs
n = 0reverseDigits(0)never loops and returns0.digitsOf(0)usesdo...while, so it runs once and returns[0]; awhileloop would return[].- Trailing zeros:
38050 - The reverse is
5083: the zeros become leading zeros and vanish. If the zeros must be kept, as in reversing a code like"00150", work with strings instead. - Negative numbers:
-38050 Math.absfirst, so every digit is between 0 and 9. Without it,-123 % 10is-3. The sign is put back at the end:-5083.- A 32-bit limit:
reverseDigits(1463847413) - The result,
3147483641, is bigger than2^31 - 1 = 2147483647. JavaScript stores it fine, but if a problem requires 32-bit answers, compare after the loop and return the fallback, usually 0. - A decimal:
digitsOf(12.5) 12.5 % 10is2.5, so it returns[1, 2.5]. These loops are for whole numbers; use the string form, or scale by a power of 10 first.
Variations and follow-ups
Twists interviewers add
Palindrome numbers without overflow
Reverse only the second half: loop while rest > reversed. Then the number is a palindrome when rest === reversed (even length) or rest === Math.floor(reversed / 10) (odd length). 4774 and 47374 pass; 4770 fails, since a number ending in 0 can't be a palindrome unless it is 0.
Digit sums and digital roots
Add each rest % 10 instead of building reversed. Repeating until one digit is left gives the digital root, which equals 1 + (n - 1) % 9 for n > 0.
Check digits
The Luhn check on card numbers reads digits from the right with % 10, doubles every second one, and subtracts 9 from any result above 9. The total must end in 0. It's the same reading loop with a different rule per digit.
Building from a digit list
digits.reduce((value, digit) => value * 10 + digit, 0) turns [5, 0, 8, 3] into 5083. It's the right half of reverseDigits on its own.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "reverse the integer", "is this number a palindrome", "sum of digits", "add digits until one is left", or "without converting to a string".
- Say the two moves out loud: "
% 10reads the last digit andMath.floor(/ 10)removes it;* 10 + digitappends a digit." Then mention the sign and the overflow check before writing code. - Test
0, a negative number, a number with trailing zeros, and one whose reverse passes the stated limit. Each one exercises a different line. - The common follow-up is "what if you can only store 32-bit integers?" Check before each append: if
reversed > (2^31 - 1 - digit) / 10, the next step would overflow.
Where frontend code uses it
The same idea in real UI code
- Checkout forms validate card numbers with the Luhn check, which doubles every second digit from the right and checks the digit sum with
% 10. - Formatting large numbers with thousands separators works on groups of three digits.
Intl.NumberFormatdoes it for you, with the right separator for each locale. - One-time-code inputs and PIN pads split a number into separate digit boxes.