javascript 26 lines · 7 steps

Validating card numbers with Luhn

How the Luhn checksum verifies a credit card number by doubling alternate digits and testing divisibility by ten.

Explained by highlit
1function isValidCardNumber(input) {
2 const digits = String(input).replace(/[\s-]/g, '');
3 
4 if (!/^\d{13,19}$/.test(digits)) {
5 return false;
6 }
7 
8 let sum = 0;
9 let double = false;
10 
11 for (let i = digits.length - 1; i >= 0; i--) {
12 let digit = digits.charCodeAt(i) - 48;
13 
14 if (double) {
15 digit *= 2;
16 if (digit > 9) {
17 digit -= 9;
18 }
19 }
20 
21 sum += digit;
22 double = !double;
23 }
24 
25 return sum % 10 === 0;
26}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1The Luhn algorithm catches most single-digit typos and adjacent transpositions cheaply, which is why cards use it.
  2. 2Doubling a digit and subtracting 9 when it exceeds 9 is a shortcut for summing the digits of the doubled value.
  3. 3Structural validation like length and format should run before the expensive checksum math.

Related explainers

typescript
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';

Wiring validation and Swagger docs in NestJS

validation openapi decorators
Intermediate 8 steps
javascript
const ROLE_PERMISSIONS = {
  admin: ['users:read', 'users:write', 'billing:read', 'billing:write'],
  manager: ['users:read', 'billing:read'],
  member: ['users:read'],

Role-based permissions middleware in Express

authorization middleware rbac
Intermediate 9 steps
javascript
function attachThousandSeparators(input, { locale = 'en-US' } = {}) {
  const formatter = new Intl.NumberFormat(locale);
  const groupSep = formatter.format(11111).replace(/\d/g, '')[0] || ',';
  const decimalSep = formatter.format(1.1).replace(/\d/g, '')[0] || '.';

Live thousand separators without losing the caret

dom intl caret-preservation
Advanced 8 steps
javascript
import { useReducer, useEffect } from "react";
 
const initialState = { status: "idle", data: null, error: null };
 

Building a data-fetching hook in React

custom-hooks usereducer data-fetching
Intermediate 9 steps
javascript
const express = require('express');
const app = express();
 
app.get('/health', (req, res) => res.json({ status: 'ok' }));

Graceful shutdown in an Express server

graceful-shutdown signal-handling connection-tracking
Advanced 9 steps
typescript
import { parsePhoneNumberFromString, CountryCode } from 'libphonenumber-js';
 
export interface NormalizedPhone {
  e164: string;

Normalizing phone numbers to E.164 in TypeScript

validation normalization error-handling
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

Validating card numbers with Luhn — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code