Luhn Algorithm (Mod 10) — Developer Guide

The Luhn algorithm (also called Mod 10) is a simple checksum formula used to validate identification numbers. Created by IBM scientist Hans Peter Luhn in 1954, it's the standard check digit mechanism for credit card numbers, IMEI numbers, ICCID/SIM numbers, and many other identification systems. This guide covers everything a developer needs: how it works, step-by-step examples, implementations in 4 languages, and common pitfalls.

Try it now: Paste any number into our Luhn Validator to instantly check if it's valid and identify the issuer.

How Luhn Works

The algorithm processes digits from right to left:

  1. Starting from the rightmost digit (the check digit), moving left
  2. Double every second digit (i.e., every digit in an even position from the right, starting with the second-to-last)
  3. If the doubled value is > 9, subtract 9 (equivalently, add the two digits of the product: e.g., 14 → 1+4 = 5, which is 14 − 9)
  4. Sum all digits (the unmodified ones + the processed ones)
  5. If the total mod 10 = 0, the number is valid

Step-by-Step Example

Let's validate the credit card number 4532 0123 4567 8917:

Position (from right)DigitActionResult
164Double: 4 × 2 = 88
155Keep as-is5
143Double: 3 × 2 = 66
132Keep as-is2
120Double: 0 × 2 = 00
111Keep as-is1
102Double: 2 × 2 = 44
93Keep as-is3
84Double: 4 × 2 = 88
75Keep as-is5
66Double: 6 × 2 = 12 → 12 − 9 = 33
57Keep as-is7
48Double: 8 × 2 = 16 → 16 − 9 = 77
39Keep as-is9
21Double: 1 × 2 = 22
1 (check)7Keep as-is (check digit)7

Sum = 8+5+6+2+0+1+4+3+8+5+3+7+7+9+2+7 = 70. 70 mod 10 = 0 → Valid.

Quick doubling trick: For any digit d, the Luhn double is: if d < 5 then 2d, else 2d − 9. This is equivalent to doubling and subtracting 9 when > 9, but faster to compute.

Generating a Check Digit

To generate a check digit for a number without one:

  1. Append 0 as a placeholder check digit
  2. Compute the Luhn sum
  3. If sum mod 10 = r, the check digit = (10 − r) mod 10

Example

Generate a check digit for 4532 0123 4567 891:

Where Luhn Is Used

ApplicationNumber LengthExampleNotes
Credit/debit cards13–19 digits4532012345678917Visa/MC/Amex/Discover all use Luhn
IMEI (phones)15 digits356938035678804Last digit is Luhn check
ICCID (SIM cards)19–20 digits89445012345678901234Starts with 89 (E.164 country code)
National provider IDs (US)10 digits1234567893NPI numbers use Luhn variant
Canadian SIN9 digits123456782Social Insurance Number
Gift card numbersVariableVaries by issuerMost retail gift cards use Luhn
Routing numbers (US)9 digits021000021ABA routing uses a different algorithm (3-weight), not Luhn

Credit Card Number Structure

4532 0123 4567 8917
|||| |||| |||| ||||
|||| |||| |||| |||└── Check digit (Luhn)
|||| |||| |||| ||└─── Account identifier (last 5 = check + account)
|||| |||| |||| └──── Account identifier
|||| |||| |||└────── Account identifier
|||| |||| ||└─────── Bank number
|||| |||| |└──────── Bank number
|||| |||| └───────── Bank number
|||| |||└─────────── Bank number
|||| ||└──────────── Bank number
|||| |└───────────── Issuer identifier
|||| └─────────────── Issuer identifier
|||└──────────────── Issuer identifier
||└───────────────── Major industry: 4 = Banking
|└────────────────── MII: 4 = Visa
└────────────────--- MII: 4 = Visa

Major Industry Identifier (MII)

MIIIndustryCommon Issuers
1AirlinesUnused in practice
2Airlines / FinancialMC (2221–2720 range)
3Travel / EntertainmentAmex (34/37), JCB (35), Diners (30/36)
4BankingVisa
5BankingMastercard (51–55)
6Merchandising / BankingDiscover (6011/65), UnionPay (62)
7PetroleumGas station cards
8TelecommunicationsTelecom cards
9National / ReservedCountry-specific

Code Implementations

Python

def luhn_validate(number: str) -> bool:
    """Validate a number using the Luhn algorithm."""
    digits = [int(d) for d in number if d.isdigit()]
    if len(digits) < 2:
        return False

    # Process from right to left
    total = 0
    for i, d in enumerate(reversed(digits)):
        if i % 2 == 1:  # Double every second digit from right
            d = d * 2
            if d > 9:
                d -= 9
        total += d

    return total % 10 == 0


def luhn_check_digit(partial: str) -> int:
    """Compute the Luhn check digit for a partial number."""
    digits = [int(d) for d in partial if d.isdigit()]
    # Insert 0 as check digit placeholder
    digits.append(0)

    total = 0
    for i, d in enumerate(reversed(digits)):
        if i % 2 == 1:
            d = d * 2
            if d > 9:
                d -= 9
        total += d

    return (10 - (total % 10)) % 10


# Test
print(luhn_validate("4532012345678917"))  # True
print(luhn_check_digit("453201234567891"))  # 7

JavaScript (Browser / Node)

function luhnValidate(number) {
    const digits = number.replace(/\D/g, '');
    if (digits.length < 2) return false;

    let sum = 0;
    let alternate = false;

    for (let i = digits.length - 1; i >= 0; i--) {
        let d = parseInt(digits[i], 10);
        if (alternate) {
            d *= 2;
            if (d > 9) d -= 9;
        }
        sum += d;
        alternate = !alternate;
    }

    return sum % 10 === 0;
}

function luhnCheckDigit(partial) {
    return luhnValidate(partial + '0')
        ? 0
        : 10 - ((computeSum(partial) + 0) % 10);
    // Simplified: append 0, compute sum, return (10 - sum%10) % 10
}

// Test
console.log(luhnValidate('4532012345678917'));  // true

C

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool luhn_validate(const char *number) {
    int len = strlen(number);
    int sum = 0;
    bool alternate = false;

    for (int i = len - 1; i >= 0; i--) {
        if (number[i] < '0' || number[i] > '9') continue;
        int d = number[i] - '0';
        if (alternate) {
            d *= 2;
            if (d > 9) d -= 9;
        }
        sum += d;
        alternate = !alternate;
    }

    return (sum % 10) == 0;
}

int main() {
    printf("%d\\n", luhn_validate("4532012345678917"));  // 1 (true)
    return 0;
}

Java

public class Luhn {
    public static boolean validate(String number) {
        String digits = number.replaceAll("\\D", "");
        if (digits.length() < 2) return false;

        int sum = 0;
        boolean alternate = false;
        for (int i = digits.length() - 1; i >= 0; i--) {
            int d = digits.charAt(i) - '0';
            if (alternate) {
                d *= 2;
                if (d > 9) d -= 9;
            }
            sum += d;
            alternate = !alternate;
        }
        return sum % 10 == 0;
    }

    public static void main(String[] args) {
        System.out.println(validate("4532012345678917"));  // true
    }
}

Common Mistakes

Mistake 1: Doubling from the left. The Luhn algorithm doubles every second digit from the right, not the left. If you double digits at positions 1, 3, 5 from the left, you'll get the wrong result when the number has an odd length.
Mistake 2: Treating Luhn as security. Luhn detects single-digit errors and most transposition errors (swapping adjacent digits). It does not provide any cryptographic security. Do not use Luhn as a password, PIN, or authentication mechanism.
Mistake 3: Not stripping non-digits. Credit card numbers are often formatted with spaces or dashes (e.g., "4532-0123-4567-8917"). Always strip non-digit characters before validation.
Mistake 4: Assuming all valid numbers are real cards. A number can pass Luhn validation without being an actual issued card. Luhn only checks the check digit, not whether the card exists. Always verify with the issuer in production systems.

When NOT to Use Luhn

Use CaseWhy Luhn FailsUse Instead
Password hashingNot a security functionbcrypt, scrypt, Argon2
Message authenticationNo key, trivially forgeableHMAC-SHA256
Detecting multi-digit errorsOnly catches ~98% of errorsDamm algorithm, Verhoeff algorithm
Bank routing numbers (US ABA)Different checksum scheme3-weight checksum (3x + 7x + x)
ISBN-13Uses different weights (1, 3 alternating)ISBN-13 weighted checksum
UPC / EAN barcodesUses modulo-10 but different weightsEAN weighted checksum

Luhn Error Detection Rates

Error TypeDetection RateNotes
Single-digit error100%All single-digit errors caught
Transposition of adjacent digits~97.7%Fails for 09 ↔ 90 only
Jump transposition (a ↔ c)~0%Not designed for this
Twin errors (aa → bb)~88.9%Caught for most digit pairs
Phonetic errors (13 ↔ 30)100%All phonetic errors caught
Random digit errors~10%~1 in 10 random strings pass

IMEI Check Digit

IMEI (International Mobile Equipment Identity) numbers use Luhn for the 15th digit (check digit):

IMEI: 356938035678804
TAC:  35693803  (Type Allocation Code, identifies model)
SN:   567880    (Serial number)
CD:   4         (Luhn check digit of TAC+SN)
IMEI on your phone: Dial *#06# on any phone to display the IMEI. The 15th digit should pass Luhn validation. Some phones show a 16-digit IMEISV (software version) which replaces the check digit with a SVN (Software Version Number) — this does NOT pass Luhn.

ICCID (SIM Card) Validation

ICCID (Integrated Circuit Card Identifier) is the 19–20 digit number on SIM cards. It starts with 89 (E.164 country code for telephony) and ends with a Luhn check digit:

ICCID: 8944 5012 3456 7890 1234
       |||  |    |    |    |   |
       89   │    │    │    │   └── Luhn check digit
       │    │    │    │    └────── Account code
       │    │    │    └─────────── Account code
       │    │    └──────────────── Issuer identifier
       │    └───────────────────── Country code (44 = UK)
       └────────────────────────── E.164 telephony country code

Summary

Luhn in 30 seconds: Double every second digit from the right, subtract 9 if > 9, sum all digits, valid if sum mod 10 = 0. Used for credit cards, IMEI, ICCID, and many ID numbers. Not a security function. Use our Luhn Validator to check any number instantly.

For related tools, see our CRC Calculator for error-detecting codes, HMAC Generator for message authentication, and Hash Digest Calculator for cryptographic hashing.