javascript
47 lines · 8 steps
How IBAN validation works in JavaScript
Validate an international bank account number by normalizing it, checking its shape and length, then running the ISO 7064 mod-97 checksum.
Explained by
highlit
1const IBAN_LENGTHS = {
2 DE: 22, FR: 27, GB: 22, ES: 24, IT: 27,
3 NL: 18, BE: 16, CH: 21, AT: 20, PT: 25,
4};
5
6function normalize(iban) {
7 return iban.replace(/\s+/g, '').toUpperCase();
8}
9
10function mod97(digits) {
11 let remainder = '';
12 for (const digit of digits) {
13 remainder += digit;
14 if (remainder.length >= 9) {
15 remainder = String(Number(remainder) % 97);
16 }
17 }
18 return Number(remainder) % 97;
19}
20
21function validateIban(input) {
22 const iban = normalize(input);
23
24 if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(iban)) {
25 return { valid: false, reason: 'malformed' };
26 }
27
28 const country = iban.slice(0, 2);
29 const expectedLength = IBAN_LENGTHS[country];
30 if (!expectedLength) {
31 return { valid: false, reason: 'unsupported_country' };
32 }
33 if (iban.length !== expectedLength) {
34 return { valid: false, reason: 'wrong_length' };
35 }
36
37 const rearranged = iban.slice(4) + iban.slice(0, 4);
38 const numeric = rearranged.replace(/[A-Z]/g, (c) => c.charCodeAt(0) - 55);
39
40 if (mod97(numeric) !== 1) {
41 return { valid: false, reason: 'checksum' };
42 }
43
44 return { valid: true, formatted: iban.match(/.{1,4}/g).join(' ') };
45}
46
47export { validateIban, normalize };
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1The mod-97 check catches transposed or mistyped digits that a simple length or format check would miss.
- 2Processing the number in chunks keeps the arithmetic within safe integer range instead of parsing one giant number.
- 3Layering cheap checks before expensive ones lets you fail fast and return a precise reason.
Related explainers
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-iban-validation-works-in-javascript-explained-javascript-6e3c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.