Implementing the Luhn Algorithm
The Luhn algorithm is widely used to validate credit card numbers. It catches mistyped card numbers before they are sent to a server.
By odd and even, we mean the digit’s position, not its value.
Worked Example
Let’s validate a Visa number: 4003600000000014
Odd positions (1st, 3rd, 5th… from right):
Even positions (2nd, 4th, 6th… from right), each multiplied by 2:
This gives us:
Since , we replace it with :
Final sum:
Since the last digit of 20 is zero (it is divisible by 10), the card number is valid.
Converting Strings to Arrays
We will implement the algorithm using array operations. First, let’s convert the card number string into an array of characters.
const cnumber = "4003600000000014";
let arr = cnumber.split("");
console.log(arr);
The split() method divides a string into an array of substrings. When the separator is an empty string (''), each character becomes a separate array element.
Since the Luhn algorithm works from right to left, we can reverse the array.
arr = arr.reverse();
Transforming Arrays with map
Our array contains strings (characters), but we need numbers. The map() method transforms each element by applying a function.
arr = arr.map(convertToInt);
function convertToInt(element) {
return parseInt(element);
}
Notice that convertToInt is passed as an argument to map(). That makes map() a higher-order function. A higher-order function is a function that takes another function as an argument, or returns a function.
Arrow Functions and Method Chaining
JavaScript has a shorter syntax for writing functions, called arrow functions. The arrow function element => parseInt(element) does the same thing as our convertToInt function, in less code.
We can also chain methods together, writing one operation after another.
const cnumber = "4003600000000014";
let arr = cnumber
.split("")
.reverse()
.map((element) => parseInt(element));
console.log(arr);
Reducing Arrays to a Single Value
The Luhn algorithm sums digits in specific ways. We need to reduce our array to a single value. The reduce() method does that.
const sum = arr.reduce(reducer, 0);
function reducer(accumulator, currentValue) {
return accumulator + currentValue;
}
console.log(sum);
The reduce() method takes two arguments:
- A reducer function that processes each element
- An initial value for the accumulator (here,
0)
The reducer function receives:
| Parameter | Description |
|---|---|
accumulator |
The running total from previous iterations |
currentValue |
The current array element |
currentIndex |
The element’s index (optional) |
Completing the Algorithm
Now let’s implement the full Luhn algorithm in our reducer.
const cnumber = "4003600000000014";
let arr = cnumber
.split("")
.reverse()
.map((element) => parseInt(element));
const sum = arr.reduce(reducer, 0);
function reducer(accumulator, currentValue, currentIndex) {
currentIndex += 1; // account for 0-based indexing
if (currentIndex % 2 === 0) {
// even position
currentValue *= 2;
if (currentValue > 9) {
currentValue -= 9;
}
}
return accumulator + currentValue;
}
if (sum % 10 === 0) {
console.log("Valid card number");
} else {
console.log("Invalid card number");
}
Try it with another valid number: 6011329933655299.
Adding a Format Check
Before running the Luhn algorithm, let’s add a quick format check to ensure the card number contains only digits and has the right length (13-16 digits). Update the card validation in handleFormSubmit.
const cnumber = document.getElementById("cnumber").value;
- if (!isValid(cnumber)) {
+ if (!/^[0-9]{13,16}$/.test(cnumber) || !isValid(cnumber)) {
window.alert("Invalid card number!");
return;
}
The pattern /^[0-9]{13,16}$/ ensures the input is between 13 and 16 digits. This is the valid range for most credit card numbers.
The Complete script.js
Here is our final validation code.
const submitBtn = document.getElementById("submit");
submitBtn.addEventListener("click", handleFormSubmit);
function handleFormSubmit(event) {
event.preventDefault();
const month = document.getElementById("month").value;
const year = document.getElementById("year").value;
if (new Date() > new Date(year, month - 1)) {
window.alert("Your card is expired!");
return;
}
const cvv = document.getElementById("cvv").value;
if (!/^[0-9]{3,4}$/.test(cvv)) {
window.alert("Invalid CVV. It must be 3 or 4 digits!");
return;
}
const cnumber = document.getElementById("cnumber").value;
if (!/^[0-9]{13,16}$/.test(cnumber) || !isValid(cnumber)) {
window.alert("Invalid card number!");
return;
}
window.alert("Thanks for the payment!");
}
function isValid(cnumber) {
let arr = cnumber
.split("")
.reverse()
.map((element) => parseInt(element));
const sum = arr.reduce(reducer, 0);
function reducer(accumulator, currentValue, currentIndex) {
currentIndex += 1;
if (currentIndex % 2 === 0) {
currentValue *= 2;
if (currentValue > 9) {
currentValue -= 9;
}
}
return accumulator + currentValue;
}
return sum % 10 === 0;
}
Notice the reducer function is defined inside isValid. In JavaScript, you can declare functions inside other functions.

Checkpoint: Commit your progress.
git add .
git commit -m "payment-03: Implement Luhn algorithm for card validation"
git push