The last digit of every card number in your wallet does no identifying work at all: it is a Luhn check digit, computed from the other digits so that a single typo makes the whole number fail. The checker above shows the entire calculation rather than a bare verdict, because the verdict alone teaches nothing: every digit in a row, the doubled positions marked, the subtract-9 step spelled out, the sum and the mod 10 result. Watch it once and you know the algorithm; that takes about a minute, which is less time than most textbook explanations need to define their notation.

A checksum from 1954

Hans Peter Luhn was an IBM researcher when he filed the design in 1954; US patent 2,950,048 was granted in 1960 and describes a hand-held mechanical device for "verifying numbers", decades before anyone typed a card number into a form. The patent has long expired, which is one of the reasons the algorithm is everywhere: it costs nothing to use and fits in a dozen lines of any language.

Its job is narrow and it has never pretended otherwise. When a person copies a number by hand, reads it over the phone or types it from a card, the mistakes follow patterns: one digit misread, two neighbours swapped. A check digit chosen so the whole number satisfies a rule catches most of those at the keyboard, before the number travels anywhere. ISO/IEC 7812 adopted Luhn as the check for payment card numbers, and from there it spread into phone hardware and national ID schemes.

What it is not: a security feature. Anyone can compute a passing number in seconds, and the tool above does it on demand. The checksum answers "was this transcribed correctly", never "does this account exist". Keeping those two questions apart is the difference between using Luhn well and misusing it.

The calculation, digit by digit

Four rules, applied from the right end of the number:

  1. The rightmost digit is the check digit. It is never doubled.
  2. Moving left, double every second digit: positions 2, 4, 6 and so on, counted from the right.
  3. A doubled result above 9 loses 9: 7 becomes 14 becomes 5. Subtracting 9 and adding the two digits of the product (1 + 4) are the same operation, so both descriptions float around; they never disagree.
  4. Add everything up. If the sum ends in 0, the number passes.

Here is 79927398713, the worked example half the literature uses, in the same layout the tool renders:

position1110987654321
digit79927398713
×21846162
adds79947697723Σ 70

70 mod 10 = 0, so the number is valid. Note what happened at position 10: the 9 doubled to 18 and shrank back to 9. Doubling with the subtract-9 rule maps the digits 0 through 9 onto 0, 2, 4, 6, 8, 1, 3, 5, 7, 9, which is a permutation, and that little fact carries the whole error-detection argument below.

One detail in the table deserves a hard stare: the number has 11 digits, so position 11 exists and is not doubled. With a 16-digit number the leftmost digit would be doubled. The doubling pattern is anchored at the right end and the left end just gets whatever parity falls out. Every implementation bug worth writing about comes from ignoring this, and the code section shows how it slips through test suites.

Computing a check digit

Generation is verification run backwards. Take the payload, pretend a check digit is already sitting at the end (which shifts every digit's position up by one, so the payload's rightmost digit now gets doubled), and sum. The check digit is whatever tops the sum up to the next multiple of ten: (10 − sum mod 10) mod 10.

For the payload 7992739871 the shifted sum is 67, so the check digit is (10 − 7) mod 10 = 3 and the complete number is 79927398713. The trailing mod 10 matters: a payload whose sum already ends in 0 needs a check digit of 0, not 10. Implementations that write 10 - sum % 10 without the outer mod produce a two-digit "digit" for one payload in ten, and that bug ships depressingly often.

The --compute flag above switches the tool into this direction: the input is treated as a payload, the digit is computed, and the worksheet shows the completed number with the new digit in green.

What Luhn catches, and what slips through

Every single-digit error is caught, at every position. In an undoubled position, changing a digit changes the sum by the difference, which is never 0 mod 10. In a doubled position the permutation above does the same work: no two digits map to the same contribution, so any change moves the sum. That is a 100% catch rate for the most common transcription mistake there is, and it is the strongest true claim you can make about Luhn.

Adjacent transpositions, the second most common mistake, are almost all caught: 88 of the 90 ordered pairs. The two survivors are 09 and 90. A 0 contributes 0 whether doubled or not, and a 9 contributes 9 whether doubled (18, minus 9) or not, so swapping them changes nothing. We verified this exhaustively while building the tool: brute-forcing all 90 adjacent swaps against the checker leaves exactly those two standing.

Beyond that, the blind spots grow. Twin errors where a repeated digit is misread as another repeated digit slip through in three cases: 22↔55, 33↔66 and 44↔77 each shift both positions by amounts that cancel mod 10. Jump transpositions (swapping two digits with one digit between them, 123 read as 321) are never caught, because both digits keep their doubling parity. And a random string of digits passes one time in ten, which is why a Luhn check on a web form filters typos nicely and filters fraud not at all.

Who uses Luhn

NumberDigitsHow the check is applied
Payment card PAN13–19last digit checks the whole number, per ISO/IEC 7812
IMEI15check digit over all 15, computed from the first 14 (3GPP TS 23.003)
Canadian SIN9plain Luhn over all nine digits
US NPI10Luhn with the constant prefix 80840 in front
Gift and loyalty cardsvariesusually plain Luhn, inherited from card infrastructure

The NPI row is the one that trips people up. A National Provider Identifier is ten digits, but the checksum runs over fifteen: the constant 80840 (the card-industry prefix assigned to US health applications) is glued in front before summing. A 10-digit NPI that fails a plain Luhn check can be perfectly valid, and the reverse. The tool knows this: paste ten digits and the findings report both readings, the plain one and the 80840-prefixed one. The standard example NPI 1234567893 makes the point nicely, since it passes with the prefix and fails without it.

The IMEI check has a footnote of its own: it covers the 15-digit IMEI only. The 16-digit IMEISV swaps the check digit for a two-digit software version number and carries no checksum, so a Luhn failure on 16 digits means nothing.

Just as useful is the list of numbers that look like they should be Luhn-checked and are not. IBANs use mod 97 over a rearranged, letter-expanded form of the account number, a different animal entirely; our IBAN validator handles those. US ABA routing numbers use a weighted 3-7-1 checksum. CVV codes are computed cryptographically by the issuer and cannot be checked, or generated, from the card number. And if what you actually need is a set of well-formed numbers to feed a payment form in staging, the credit card test numbers page has the standard set per brand, all of them Luhn-valid by construction.

The same algorithm in JavaScript, Python and SQL

First, the bug to design against, because it accounts for most broken Luhn code in the wild: doubling anchored at the wrong end. "Double every second digit" reads naturally as "starting from the first", and an implementation that doubles indexes 0, 2, 4 from the left produces the correct pattern for every even-length number and the wrong one for every odd length. The consequence is a test-suite trap we can reproduce on demand: the left-anchored version happily validates 16-digit Visa test numbers and then rejects every valid 15-digit Amex and every valid IMEI. If your test vectors are all the same length, this bug is invisible. Keep 4111111111111111 (16 digits) and 79927398713 (11 digits) in the suite and it cannot survive.

JavaScript, walking backwards with a flip-flop flag so the anchoring is right by construction:

const luhnValid = (num) => {
  const digits = num.replace(/[\s-]/g, '');
  let sum = 0;
  for (let i = digits.length - 1, double = false; i >= 0; i--, double = !double) {
    let v = Number(digits[i]);
    if (double) { v *= 2; if (v > 9) v -= 9; }
    sum += v;
  }
  return sum % 10 === 0;
};

Keep the input a string throughout. A 16-digit value brushes against Number.MAX_SAFE_INTEGER, 19-digit PANs are past it, and converting also eats leading zeros. The same reasoning applies in every language with fixed-width integers.

Python, reversing first so the slice syntax does the position bookkeeping:

def luhn_valid(num: str) -> bool:
    digits = [int(c) for c in num if c.isdigit()][::-1]
    total = sum(digits[0::2])            # check digit side, never doubled
    for d in (2 * x for x in digits[1::2]):
        total += d - 9 if d > 9 else d
    return total % 10 == 0

The [::-1] is doing the safety work: after reversing, even indexes are the undoubled positions no matter how long the number is. For the compute direction, sum num + "0" the same way and return (10 - total % 10) % 10.

SQL has no loops worth writing, but it does have set operations, and the digit-splitting trick makes Luhn a single query in PostgreSQL:

-- PostgreSQL: i = 1 is the rightmost digit, doubled when i is even
SELECT sum(CASE WHEN i % 2 = 0
                THEN CASE WHEN 2 * d > 9 THEN 2 * d - 9 ELSE 2 * d END
                ELSE d END) % 10 = 0 AS luhn_valid
FROM (SELECT i, substr(reverse('79927398713'), i, 1)::int AS d
      FROM generate_series(1, length('79927398713')) AS i) digits;

MySQL 8 and SQL Server manage the same shape with a recursive CTE in place of generate_series. This is genuinely useful for data cleaning: an imported column of card or IMEI numbers can be screened with one WHERE NOT luhn_valid(col) instead of a round trip through a script, and the same expression works in a CHECK constraint if bad rows should never land at all.

Whichever language: the tool above and these three snippets agree with each other on every vector we threw at them, both parities included, which is the property to test for before trusting any Luhn code you find online.

Luhn vs mod 97, Verhoeff and Damm

Luhn is the oldest and weakest of the practical check-digit schemes, which raises the fair question of why it is still everywhere. The comparison:

Luhn (1954)mod 97, ISO 7064Verhoeff (1969)Damm (2004)
check digits1211
single-digit errorsallallallall
adjacent transpositions88 of 90allallall
needsarithmeticbig-int or chunked moddihedral group tablesone quasigroup table
used bycards, IMEI, SIN, NPIIBAN, ISIN, creditor IDsAadhaar, some serialsnewer custom schemes

Mod 97 buys its extra strength with two check digits and arithmetic on a number too large for 64-bit integers unless you reduce in chunks; that trade fits IBANs, where the number is long anyway and validation happens in software. Verhoeff closed the 09↔90 gap back in 1969 using multiplication in the dihedral group D5, at the price of three lookup tables nobody can reconstruct from memory. Damm gets the same coverage from a single quasigroup table and no special cases, and is the sensible pick for a new numbering scheme designed today.

Luhn persists for a reason that has nothing to do with its error catalogue: it is baked into ISO/IEC 7812 and into seventy years of issued cards, provisioned IMEIs and printed ID cards. A checksum is a contract between everyone who generates numbers and everyone who checks them, and renegotiating that contract across the entire payment industry would cost more than the 09↔90 gap ever has. So the algorithm from 1954 keeps its job, and knowing exactly what it does and does not promise (all typos of one digit, most swaps, no security) is what this page is for.

Check digit questions

How does the Luhn algorithm work step by step?

Start at the rightmost digit and move left: double every second digit (positions 2, 4, 6 and so on, counted from the right), subtract 9 from any doubled result above 9, add everything up, and take the total mod 10. A result of 0 means the number passes; anything else means at least one digit is wrong. For 79927398713 the total is 70, and 70 mod 10 = 0, so it passes. To generate a check digit instead of verifying one, run the same sum over the payload as if a digit were already appended and use (10 − sum mod 10) mod 10.

How do I check a credit card number with the Luhn algorithm in Python?

Reverse the digits, sum the even indexes as they are, double the odd indexes and subtract 9 whenever the product exceeds 9, then test the total against mod 10: def luhn(n): d = [int(c) for c in n if c.isdigit()][::-1]; return (sum(d[0::2]) + sum(x*2 - 9 if x*2 > 9 else x*2 for x in d[1::2])) % 10 == 0. Test it with 4111111111111111 and 79927398713 (both True) plus one number with a digit changed (False); if the odd-length vector fails, your doubling is anchored at the wrong end.

How do I implement the Luhn algorithm in JavaScript?

Walk the string from the end with a boolean that flips on every step: double when the flag is set, subtract 9 above 9, accumulate, and compare sum % 10 === 0. Strip spaces and dashes first, and keep the digits as a string rather than converting to a number: 16 digits already brush against Number.MAX_SAFE_INTEGER and 19-digit card numbers are past it, and a leading zero would vanish in the conversion. The article above has the complete function; it is eight lines with no dependencies.

Can I validate a Luhn checksum in SQL?

Yes, without procedural code: split the reversed number into digits with generate_series and substr (PostgreSQL) or a recursive CTE (MySQL 8, SQL Server), double the digits at even positions, subtract 9 above 9, and compare the sum against mod 10. The article above has a complete PostgreSQL query. For bulk-checking an imported column this beats exporting to a script; wrap the expression in a function and it works in a WHERE clause or a CHECK constraint.

Which numbers use the Luhn algorithm?

Payment card numbers (the 13 to 19 digit PAN, per ISO/IEC 7812), phone IMEIs (15 digits, checked over all 15), Canadian Social Insurance Numbers (9 digits) and US National Provider Identifiers (10 digits, checked with the constant prefix 80840 in front). Many gift card and loyalty schemes reuse it too, because the card infrastructure they ride on already expects it. Similar-looking numbers that do not use Luhn: IBANs (mod 97), US ABA routing numbers (weighted 3-7-1 checksum) and card CVV codes, which are cryptographic and cannot be derived from the card number.

Why does my Luhn implementation double the wrong digits?

Because "double every second digit" is anchored at the right end of the number, and broken implementations count from the left. The rightmost digit is the check digit and is never doubled; its left neighbour always is. Counting from the left happens to produce the same pattern for even-length numbers, so a test suite full of 16-digit Visa numbers passes and the bug surfaces only on 15-digit Amex or IMEI input. Iterate from the end, or reverse the string first, and always keep one even-length and one odd-length test vector: 4111111111111111 and 79927398713 both pass a correct implementation.

Does a valid Luhn checksum mean the card number is real?

No. The checksum proves the digits are internally consistent, nothing else; any payload can be turned into a passing number with one subtraction. Whether a card was issued, is active and has funds is known only to the issuing bank and answered only by an authorization request. Luhn sits at the front of that pipeline to reject typos before they cost an API call, and that is the entire claim a green checkmark makes, in this tool and everywhere else.

What errors does the Luhn algorithm not catch?

Swapping the adjacent digits 09 to 90 (or back) is the famous blind spot: both orders contribute 9 to the sum, so the checksum stays valid. The twin substitutions 22↔55, 33↔66 and 44↔77 also pass, as does swapping two digits with another digit between them, because both keep their doubling position. And since a random digit string passes one time in ten, two independent typos have roughly a 10% chance of cancelling out. Everything else is caught: every single-digit error and 88 of the 90 possible adjacent transpositions.

How is the IMEI check digit calculated?

The 15th digit of an IMEI is a Luhn check digit over the first 14 (the 8-digit type allocation code plus the 6-digit serial), defined in 3GPP TS 23.003. Compute it like any Luhn check digit: double every second digit of the 14-digit payload starting from its right end, subtract 9 above 9, sum, and take (10 − sum mod 10) mod 10. Dialing *#06# shows the full 15-digit IMEI on the device itself. The 16-digit IMEISV variant replaces the check digit with a two-digit software version and has no checksum at all.

How do I calculate a Luhn check digit for a payload?

Append an imaginary 0, run the normal Luhn sum, and the check digit is (10 − sum mod 10) mod 10. For the payload 7992739871 the shifted sum is 67, so the check digit is (10 − 7) mod 10 = 3 and the complete number is 79927398713. The final mod 10 is not decoration: when the sum is already a multiple of 10, 10 − 0 gives 10 and the check digit has to be 0, a classic off-by-one in homegrown implementations. The --compute flag in the tool above does exactly this and shows the worksheet for the completed number.