Skip to content
Card BIN Checker
luhncard-validationchecksumpaymentsdevelopers

How the Luhn Algorithm Validates Card Numbers

The Luhn algorithm is the mod-10 checksum that catches mistyped card numbers before checkout. Here's how it works, worked by hand, with its real limits.

7 min read

Type a card number into almost any checkout form and it gets a first sanity check before it ever leaves your browser — often before you've even clicked "Pay." That check is the Luhn algorithm, a tiny mod-10 checksum invented by IBM engineer Hans Peter Luhn in the 1950s. It doesn't ask a bank anything. It just does arithmetic on the digits themselves to catch the mistakes humans actually make: a mistyped digit, a swapped pair. This article walks through exactly how it works, with a fully worked example that matches the code powering our Luhn Checker.

By the end you'll be able to compute a Luhn checksum by hand, understand why it catches most typos, know where else it shows up beyond payment cards, and — just as importantly — understand what it cannot tell you.

What the Luhn algorithm actually checks

The Luhn algorithm is a checksum: the last digit of a valid number is a check digit chosen so that a specific weighted sum over all the digits comes out divisible by 10. Because the check digit is derived from the others, corrupting any single digit almost always breaks that divisibility — which is how the error gets caught.

On a payment card, the check digit is the very last digit of the Primary Account Number (PAN). The other digits encode the network, the issuer, and the account. The Luhn check digit is appended so that machines can reject fat-fingered numbers instantly, with no network round-trip.

Checksum, not lookup

Luhn validation is pure arithmetic on the digits you already have. It never contacts a bank, a card network, or any server. That's why our tools run it entirely in your browser — your card number is never sent or stored.

The algorithm, step by step

Here is the complete procedure. It's easiest to work from the rightmost digit to the left, because the doubling pattern is anchored at the right end where the check digit lives.

  1. Starting from the rightmost digit and moving left, look at every second digit. The rightmost digit itself is not doubled; the one to its left is, and so on, alternating.
  2. Double each of those selected digits. If doubling produces a two-digit result (anything above 9), subtract 9. For example 8 × 2 = 16 → 16 − 9 = 7. (Subtracting 9 is a shortcut for adding the two digits: 1 + 6 = 7.)
  3. Sum every digit: the doubled-and-reduced values plus the untouched digits.
  4. If that total is divisible by 10 (sum mod 10 == 0), the number passes. Otherwise it fails.

That's the whole thing. There is no secret table, no lookup, no key — just alternate doubling and a mod-10 test. The one detail people get wrong is which digits double: it's every second digit counting from the right, which means the parity depends on the number's length.

A fully worked example

Let's run the standard Visa test number 4111 1111 1111 1111 through it by hand. Written out, the digits are 4 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1. Processing right-to-left, the rightmost 1 is position 0 (not doubled), the next 1 is doubled, and so on:

Digit (right → left):  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  4
Double every 2nd?      -  ×2 -  ×2 -  ×2 -  ×2 -  ×2 -  ×2 -  ×2 -  ×2
Value after doubling:  1  2  1  2  1  2  1  2  1  2  1  2  1  2  1  8

Sum = 1+2+1+2+1+2+1+2+1+2+1+2+1+2+1+8 = 30
30 mod 10 = 0  →  PASSES ✓

The sum is 30, which is divisible by 10, so 4111111111111111 passes the Luhn check. Notice the leading 4: it sits at a doubled position, 4 × 2 = 8, which stays a single digit so no subtraction is needed.

To see the subtract-9 rule in action, take the classic textbook number 79927398713. Doubling 8 → 16 → 7 and 9 → 18 → 9 gives a total of 70, which is also divisible by 10 — so it passes too. Now swap two adjacent digits to 97927398713 and the sum shifts to 68: 68 mod 10 = 8, and the number is rejected. That single swap is exactly the kind of typo Luhn is built to catch.

Paste any number and watch every doubling step and the running total, live in your browser.

Try the Luhn Checker

How to compute the check digit yourself

If you have a payload without its check digit and want to append the correct one, the trick is to run the same weighted sum — but this time treat the next position (where the check digit will go) as a doubled slot — then choose the digit that rounds the total up to the next multiple of 10. The formula is (10 − (sum mod 10)) mod 10. Here's a compact, dependency-free implementation that mirrors our engine:

// True if a digit string passes the Luhn (mod-10) checksum.
function luhnCheck(digits) {
  if (!/^\d+$/.test(digits)) return false;
  let sum = 0;
  let double = false; // rightmost digit is NOT doubled
  for (let i = digits.length - 1; i >= 0; i -= 1) {
    let d = digits.charCodeAt(i) - 48; // fast char → int
    if (double) {
      d *= 2;
      if (d > 9) d -= 9; // shortcut for summing the two digits
    }
    sum += d;
    double = !double; // flip on every step
  }
  return sum % 10 === 0;
}

// Compute the check digit for a payload (which excludes it).
function computeCheckDigit(payload) {
  let sum = 0;
  let double = true; // the check digit will sit at a doubled position
  for (let i = payload.length - 1; i >= 0; i -= 1) {
    let d = payload.charCodeAt(i) - 48;
    if (double) { d *= 2; if (d > 9) d -= 9; }
    sum += d;
    double = !double;
  }
  return (10 - (sum % 10)) % 10;
}

luhnCheck("4111111111111111"); // → true
computeCheckDigit("411111111111111"); // → 1

Why `double` flips like that

In luhnCheck the rightmost digit is the check digit, so it starts undoubled (double = false). In computeCheckDigit there is no check digit yet, so the next slot to the right would be doubled — which means the last real digit of the payload is not, and we start with double = true. Getting this starting parity right is the whole game.

Why it catches the errors it does

Luhn wasn't designed to stop fraud — it was designed to stop transcription errors, the ones people make reading a number off a card or a screen. It's remarkably good at exactly two common cases:

  • Any single wrong digit. Change one digit and the weighted sum changes by a nonzero amount that isn't a multiple of 10, so the mod-10 test fails. Luhn catches 100% of single-digit errors.
  • Most adjacent transpositions. Swapping two neighbouring digits (e.g. 12 → 21) is caught for every pair except 09 ↔ 90, because doubling makes those two produce the same contribution. Everything else is flagged.

Those two error types cover the large majority of real-world mistakes, which is why a checksum this simple has survived for seventy years. It's cheap to compute, needs no lookup, and gives instant feedback — perfect for a first pass before a payment even reaches the network.

Where else Luhn shows up

Because it's simple and effective, Luhn is standardized (ISO/IEC 7812 for card issuer numbers) and reused far beyond payment cards:

  • IMEI numbers — the 15-digit identifiers for mobile phones use a Luhn check digit.
  • National and government IDs — several countries' identity, tax, or health-card numbers embed a Luhn digit, including Canadian Social Insurance Numbers and US NPI provider identifiers.
  • SIM card ICCIDs and various product and account codes that need a cheap typo guard.

If you ever see a number that gets rejected the instant you finish typing it — before anything could plausibly have been looked up — there's a good chance a Luhn (or Luhn-like) checksum is doing the work.

What Luhn does NOT tell you

This is the part people most often get wrong. Passing the Luhn check does not mean a card is real, active, or has funds. It only means the digits are internally consistent. You can generate infinitely many Luhn-valid strings that no bank ever issued.

A robust structural check therefore layers Luhn together with two other tests, which is exactly what our Card Validator does:

  • Characters — the input is all digits (letters, spaces, and dashes are stripped first).
  • Length — the digit count matches the detected network. Visa numbers are 13, 16, or 19 digits; Mastercard and American Express are fixed at 16 and 15 respectively.
  • Luhn — the mod-10 checksum passes.

Even all three passing only tells you the number is well-formed — not that it corresponds to a spendable card. The only way to know a card is genuinely usable is an authorization through the payment network, which no client-side tool can (or should) do. For definitions of terms like PAN, BIN, and check digit, see the glossary.

Don't confuse valid with real

A Luhn-valid number is a structurally plausible number, nothing more. Never treat a passing checksum as proof that a card exists or that a payment will succeed — always rely on the actual authorization response from your payment processor.

Want to see all of this on live numbers? Run any digits through the Luhn Checker for a step-by-step breakdown, or use the Card Validator to combine the checksum with network and length checks in one pass — all locally, with nothing ever leaving your browser.