Luhn algorithm: calculate and check a check digit
Run the Luhn checksum by hand, see exactly which digits are doubled, and learn what a valid check digit can and cannot prove.
The Luhn algorithm checks a decimal identifier by doubling every second digit from the right, subtracting 9 when a doubled digit exceeds 9, and summing the results. A complete number passes when the sum is divisible by 10. It detects many input mistakes, not fraud or account existence.
A check digit is like the last number in a puzzle: the earlier numbers determine which final digit makes the total end in zero. If someone mistypes a digit, the puzzle often stops fitting.
The Luhn algorithm is a checksum, not an authentication method. It gives an identifier one final digit that catches many accidental typing errors. The calculation is easy to get wrong when a description says only “double every other digit” and leaves out which end to start from.
Check a complete number
Use invented ASCII digits only. Edit the complete number, then choose Check.
JavaScript controls are needed to edit and check an input. The worked calculation remains readable without them.
Arithmetic practice only. A checksum does not prove that an account exists or authorise a payment. Inputs are not stored or sent.
Checked number: 79927398713.
Transformed digits: 7 · 9 · 9 · 4 · 7 · 6 · 9 · 7 · 7 · 2 · 3
Sum: 70. The checksum passes.
Check a number#
Use the illustrative complete number 79927398713. Starting at the right, keep the final 3 unchanged. Moving left, double the next digit, then leave one unchanged, and repeat. Whenever a doubled value exceeds 9, subtract 9. The transformed digits, from left to right, are 7, 9, 9, 4, 7, 6, 9, 7, 7, 2, 3. Their sum is 70, so the number passes because 70 % 10 == 0.
def passes_luhn(text: str) -> bool:
if not text or not text.isascii() or not text.isdecimal():
return False
total = 0
for position, digit in enumerate(reversed(text)):
value = int(digit)
if position % 2 == 1:
value *= 2
if value > 9:
value -= 9
total += value
return total % 10 == 0
The position starts at zero on the existing check digit. That is why odd positions are doubled in this complete-number validator.
Generate the check digit#
Start with the digits before the final check digit: the payload. Temporarily append 0 and apply the complete-number calculation above. The placeholder stays unchanged, so the rightmost payload digit is doubled. This avoids maintaining a second, easy-to-reverse rule for alternating positions.
Choose the digit that brings the sum to a multiple of 10. If the temporary sum is 67, the digit is 3; if the sum already ends in zero, the digit is 0.
Before calculating, predict which digit completes the illustrative payload 7992739871. Generate it below, then check the completed number. Change only its final digit to 4 and check again. Use invented digits for arithmetic practice, not real payment or health identifiers.
Generate a check digit
Use invented ASCII digits only. Generate from 1–31 digits, then check or edit the completion.
Append a zero: 79927398710.
Doubled positions from the left: 2, 4, 6, 8, 10.
Each digit’s contribution, from left to right
- Position 1: 7 → 7 (keep).
- Position 2: 9 → 9 (double, then subtract 9).
- Position 3: 9 → 9 (keep).
- Position 4: 2 → 4 (double).
- Position 5: 7 → 7 (keep).
- Position 6: 3 → 6 (double).
- Position 7: 9 → 9 (keep).
- Position 8: 8 → 7 (double, then subtract 9).
- Position 9: 7 → 7 (keep).
- Position 10: 1 → 2 (double).
- Position 11: 0 → 0 (zero placeholder; keep).
Provisional sum: 67.
Check digit: 3, because (10 − (67 % 10)) % 10 = 3.
Generated completion: 79927398713.
JavaScript controls are needed to edit and check an input. The worked calculation remains readable without them.
Arithmetic practice only. A checksum does not prove that an account exists or authorise a payment. Inputs are not stored or sent.
Append digit 3 to make 79927398713, using provisional sum 67. Choose Check to test its checksum.
For this payload, the temporary-zero calculation totals 67. Appending 3 makes the total 70, so the completed 79927398713 passes. Changing only the final digit to 4 adds one to the total: 71 fails. The earlier contributions stay the same because the number’s length has not changed.
def luhn_check_digit(payload: str) -> str:
if not payload or not payload.isascii() or not payload.isdecimal():
raise ValueError("payload must contain ASCII decimal digits")
provisional = payload + "0"
# Reuse the same transform as passes_luhn, but return its sum.
total = sum((int(d) * 2 - 9 if int(d) * 2 > 9 else int(d) * 2)
if i % 2 else int(d)
for i, d in enumerate(reversed(provisional)))
return str((-total) % 10)
Both Python examples restrict input to nonempty ASCII decimal digits. The generator rejects invalid payloads instead of treating empty or Unicode-digit input as an identifier.
The browser generation exercise accepts 1–31 payload digits and keeps leading zeros; appending the check digit fits the checker’s 32-digit limit. The Python examples do not impose that length limit. The sample number is for arithmetic practice and says nothing about a real account.
What a pass means#
Some mistakes still pass. The illustrative numbers 1099 and 1909 both produce a total of 20: swapping the adjacent 09 to 90 leaves their combined contribution unchanged. Try both in the complete-number checker. A passing checksum does not guarantee that the digits match the original.
A passing checksum is useful for finding common transcription mistakes before sending a request. It cannot prove that an identifier was issued, belongs to the person entering it, or is safe to use. Treat it as cheap input validation and let the responsible service decide authorisation. A deterministic finite automaton can recognise an allowed input pattern, but a pattern match likewise cannot establish that an identifier is genuine. A government implementation profile documents the Luhn check for identification numbers.
Actual permission is a separate decision. Scoped credentials show what limiting a caller’s authority looks like after an input has passed validation.
Questions people also ask
2 QUESTIONSIs a number that passes Luhn a real payment card?
No. Luhn only tests a checksum. It does not establish that an account exists, is active, or can authorise a payment.
Which side do I start doubling from?
For a complete number, keep the rightmost check digit unchanged and double the digit immediately to its left, then every second digit moving left. For a payload without its check digit, double its rightmost digit first, then every second digit moving left. Which positions are doubled when counting from the left depends on the payload length.