Quick answer

A Luhn formula check test takes a number, doubles every second digit counting from the right, subtracts 9 from any doubled value above 9, adds all the digits together, and checks whether the total is a multiple of 10. Total ends in zero: the number passes the mod-10 checksum. Total ends in anything else: at least one digit is wrong or out of position. That is the whole test. It takes a few lines of code and finishes in microseconds, which is why it runs anywhere a number gets typed by a human.

more on this topic

Why the check exists

Hans Peter Luhn, an IBM researcher, patented the method in the 1950s. He was not trying to build a security control. He wanted to catch the mistakes people actually make at a keyboard: one digit typed wrong, two adjacent digits swapped, a digit dropped during transcription. The formula catches every single-digit error and most transpositions, and it does so without any lookup table or secret key. That combination of cheap and useful kept it in circulation long after the patent expired.

luhn formula check test

The test, step by step

  1. Take the number as a string of digits. Strip spaces, hyphens, and dots first.
  2. Starting at the rightmost digit and moving left, double the value of every second digit. The last digit itself is never doubled.
  3. If a doubled value is 10 or higher, subtract 9 from it. (16 becomes 7, 18 becomes 9, 10 becomes 1.)
  4. Add the doubled values and the untouched values together.
  5. Divide the sum by 10. A remainder of 0 means the number passes.

Some implementations reverse the string first, double the odd indexes, then sum. Same result, different bookkeeping. Pick one convention and stay with it, because mixing the two is the most common bug I see in hand-rolled validators.

read more

Worked example

Number: 79927398713. Reading right to left, the digits at positions 2, 4, 6, 8, 10 get doubled: 1 becomes 2, 8 becomes 7, 3 becomes 6, 2 becomes 4, 9 becomes 9. The rest stay as they are. Sum: 3 + 2 + 7 + 7 + 9 + 6 + 7 + 4 + 9 + 9 + 7 = 70. Seventy is a multiple of 10, so the number passes. Change any single digit and the sum moves off a multiple of 10, which is exactly the property the check digit buys you.

read more

Test vectors for your implementation

  • 4242424242424242 passes (sum 80).
  • 4242424242424241 fails (sum 79).
  • 79927398713 passes (sum 70).
  • 79927398710, 79927398711, 79927398712 all fail.
  • 0 passes. So does an empty string in most libraries, which is usually the wrong behavior for a payment form.

Edge cases that break naive code

  • Leading zeros. Parse to an integer and 0004 becomes 4, which shifts every doubling position. Treat the input as a string end to end.
  • Non-digit characters. Decide up front whether to strip them or reject the input. Silent stripping hides typos that the Luhn test would otherwise catch.
  • Very long inputs. The formula has no length limit, so a 40-digit string will still produce a pass or fail. Length limits belong in your own validation rules, not in the checksum.
  • Unicode digits. A character like a full-width 4 is not the ASCII digit 4, and converting it carelessly gives you a value that quietly passes.

What a passing test does not tell you

This is the part people skip. A Luhn pass means the digits are internally consistent. It says nothing about whether the number was ever issued, whether it is active, or whether it belongs to the person typing it. A randomly generated string has roughly a 1 in 10 chance of passing. The formula also misses the 09 to 90 transposition, since both orderings produce the same sum. Treat the result as a typo filter, not a validity check, and never as proof of anything.

Implementing it

Loop from the last character to the first, keep a running sum, and use the loop index parity to decide whether to double. Convert each character with a digit-value function that rejects anything outside 0 through 9. Return true when the sum modulo 10 equals zero. Keep it pure, no I/O, no formatting, and write the test vectors above as unit tests before you wire it into a form. That way a regression in the parity logic shows up as a failing test instead of a user complaint.