cf.completefrontend
MATH & GEOMETRY · FOUNDATION

Working with digits

Read, reverse, or sum a number's digits with % 10 and Math.floor(n / 10), without turning it into a string.

Time O(d)Space O(1) for reversing, O(d) for the digit list3 practice problemsStep-through picture

The 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.

How the example maps to the code
In the exampleIn the code
The rightmost bookrest % 10
The shelf with that book removedMath.floor(rest / 10)
Adding a book at the right end of the new shelfreversed * 10 + digit
The first shelf is emptyrest === 0

From the slow way to working with digits

  1. Start with the string way: String(n).split("").reverse().join(""). For 38050 it gives "05083", and Number turns that into 5083. But for -38050 it gives "05083-": Number turns that into NaN, and parseInt gives 5083 with the sign lost. It also builds a string and an array just to move digits around.
  2. Arithmetic can do the same with two operations. n % 10 is 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.
  3. Building goes the other way. value * 10 shifts every digit of value one place left and leaves a 0 at the end; adding a digit fills that place. So value * 10 + digit appends a digit on the right.
  4. Now combine them. Take digits off the right end of rest and append each one to the right end of reversed: the first digit taken off becomes the first digit of the result, so the order flips. Work on Math.abs(n) and put the sign back at the end. That loop is reverseDigits, and the same reading loop with unshift is digitsOf.

How it works

  1. Work with rest = Math.abs(n), and deal with the sign at the end.
  2. While rest > 0, read the last digit: rest % 10.
  3. Use the digit: add it to a sum, push it to a list, or append it to reversed with reversed * 10 + digit.
  4. Remove it: rest = Math.floor(rest / 10).
  5. When rest is 0, every digit was seen. Put the sign back if needed.
  6. To list digits left to right, put each new digit at the front (unshift), and use a do...while loop so 0 gives [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.

Loading the picture…
See every step at onceThe same run, laid out side by side
Drawing every step…

The code

Point at a note to light up the lines it explains.

  1. function digitsOf(n) {
  2. const digits = [];
  3. let rest = Math.abs(n);
  4. do {
  5. digits.unshift(rest % 10);
  6. rest = Math.floor(rest / 10);
  7. } while (rest > 0);
  8. return digits;
  9. }
  10. function reverseDigits(n) {
  11. let rest = Math.abs(n);
  12. let reversed = 0;
  13. while (rest > 0) {
  14. reversed = reversed * 10 + (rest % 10);
  15. rest = Math.floor(rest / 10);
  16. }
  17. return n < 0 ? -reversed : reversed;
  18. }

How fast it is

TimeO(d)
Extra memoryO(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 caseO(1)

    A one-digit number, or 0, where reverseDigits doesn't loop at all.

  • Average caseO(d)

    One step per digit: a number with d digits takes d steps, which is about log₁₀ n.

  • Worst caseO(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 / 10 without Math.floor. The number becomes a decimal like 123.4 and the loop never reaches exactly 0.
  • Using % on a negative number and expecting a positive digit. -123 % 10 is -3. Take Math.abs first.
  • Forgetting that 0 has one digit. A while loop 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.

Not started

0 of 3 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.

Start practicing

Go 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

Steprestrest % 10reversedWhat happensWhy
138050-0rest = Math.abs(n)The sign is set aside. reversed starts at 0, a number with no digits yet.
23805000rest = 3805The last digit is 0. 0 × 10 + 0 is still 0, so this zero will become an invisible leading zero.
3380555rest = 3800 × 10 + 5 = 5. The first real digit of the answer.
4380050rest = 385 × 10 + 0 = 50. A zero in the middle is kept, because it shifts the 5 one place left.
5388508rest = 350 × 10 + 8 = 508.
6335083rest = 0508 × 10 + 3 = 5083. Math.floor(3 / 10) is 0.
70-5083return -5083rest 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
ApproachTimeExtra memoryChoose it when
Working with digitsO(d)O(1)Numbers that fit exactly, when you want no string conversions and full control of signs and zeros.
Convert to a stringO(d)O(d)Readability matters more than speed, or leading zeros must be kept. Handle the sign yourself.
BigInt digit loopO(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

1101001k10k100k1M116324864input size n
Steps as the input grows, for Working with digits and the other approaches, on a scale where each line up is 10 times more steps.
  • Working with digits, Convert to a string O(d)
  • BigInt digit loop O(d²)
About how many steps for bigger inputs
Approachn = 10n = 1,000n = 1,000,000
Working with digits101,0001 million
Convert to a string101,0001 million
BigInt digit loop1001 million1 trillion

Inputs that need care

5 tricky inputs
n = 0
reverseDigits(0) never loops and returns 0. digitsOf(0) uses do...while, so it runs once and returns [0]; a while loop 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.abs first, so every digit is between 0 and 9. Without it, -123 % 10 is -3. The sign is put back at the end: -5083.
A 32-bit limit: reverseDigits(1463847413)
The result, 3147483641, is bigger than 2^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 % 10 is 2.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: "% 10 reads the last digit and Math.floor(/ 10) removes it; * 10 + digit appends 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.NumberFormat does it for you, with the right separator for each locale.
  • One-time-code inputs and PIN pads split a number into separate digit boxes.

Related algorithms