<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rosario vitale</title>
    <description>The latest articles on DEV Community by rosario vitale (@rosariov25).</description>
    <link>https://dev.to/rosariov25</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4015781%2Fee4c48a4-225a-4fa3-9da6-99816603cd69.png</url>
      <title>DEV Community: rosario vitale</title>
      <link>https://dev.to/rosariov25</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rosariov25"/>
    <language>en</language>
    <item>
      <title>How to Validate an IBAN (MOD-97 Check Digit, Explained)</title>
      <dc:creator>rosario vitale</dc:creator>
      <pubDate>Sun, 05 Jul 2026 08:23:59 +0000</pubDate>
      <link>https://dev.to/rosariov25/how-to-validate-an-iban-mod-97-check-digit-explained-1436</link>
      <guid>https://dev.to/rosariov25/how-to-validate-an-iban-mod-97-check-digit-explained-1436</guid>
      <description>&lt;p&gt;You typed your IBAN, hit send, and the bank threw it straight back: &lt;em&gt;invalid IBAN&lt;/em&gt;. The number looks exactly right — same letters, same digits you copied off the invoice — but the transfer won't go through. Before you blame the bank, know this: every IBAN carries a built-in self-check, and it just did its job. Those two digits right after the country code are a checksum whose whole purpose is to catch a single typo or two swapped characters before your money moves to the wrong account. If you just need the payment to work, the fix is usually one mistyped character. If you're a developer wiring up validation, here's exactly how the MOD-97 algorithm does it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the IBAN check digits?
&lt;/h2&gt;

&lt;p&gt;An IBAN is a country code, two check digits, then the BBAN — your domestic bank and account number. Those two check digits (positions 3 and 4) aren't part of your account. They're computed from the rest of the number using an algorithm called MOD-97, formally ISO 7064, mod 97-10. To validate an IBAN, you run the same math and confirm the result comes out to exactly &lt;strong&gt;1&lt;/strong&gt;. If it doesn't, something in the number is wrong.&lt;/p&gt;

&lt;p&gt;One caveat worth stating up front: passing MOD-97 only proves the number is internally consistent and the correct length. It does &lt;strong&gt;not&lt;/strong&gt; confirm that the bank, branch, or account actually exists or is open. That requires a separate bank-directory or account-verification lookup — not the check-digit arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MOD-97 algorithm, step by step
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normalize.&lt;/strong&gt; Strip spaces and uppercase every letter. Check the length matches the fixed length for the country code (Germany is 22, UK 22, France 27, Norway 15, Malta 31).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate.&lt;/strong&gt; Move the first four characters — country code plus check digits — from the front to the back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letters to numbers.&lt;/strong&gt; Replace each letter with two digits: A=10, B=11, … Z=35 (that's &lt;code&gt;ord(L) - 55&lt;/code&gt;). Digits stay as they are. You now have one long numeric string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mod 97.&lt;/strong&gt; Read that string as a single huge integer and take its remainder modulo 97. It's far bigger than a 64-bit int, so use a bignum, or compute the remainder piecewise, left to right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide.&lt;/strong&gt; A remainder of 1 means valid. Anything else means the number is corrupted.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A worked example — DE89370400440532013000
&lt;/h2&gt;

&lt;p&gt;Take &lt;code&gt;DE89370400440532013000&lt;/code&gt; (Germany, so it must be 22 characters — it is).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rotate:&lt;/strong&gt; move &lt;code&gt;DE89&lt;/code&gt; to the end → &lt;code&gt;370400440532013000DE89&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Convert letters:&lt;/strong&gt; D=13, E=14, so the tail &lt;code&gt;DE89&lt;/code&gt; becomes &lt;code&gt;131489&lt;/code&gt;. The full numeric string is &lt;code&gt;370400440532013000131489&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Mod 97:&lt;/strong&gt; as one big integer, &lt;code&gt;370400440532013000131489 mod 97 = 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No bignum? Do it in chunks, prefixing each running remainder onto the next digits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;370400440 mod 97 = 23&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;235320130 mod 97 = 70&lt;/code&gt;  (the previous &lt;code&gt;23&lt;/code&gt; in front of the next digits &lt;code&gt;5320130&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;700013148 mod 97 = 38&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;389 mod 97 = 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both roads land on &lt;strong&gt;1&lt;/strong&gt;, so &lt;code&gt;DE89370400440532013000&lt;/code&gt; is &lt;strong&gt;valid&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  In code (Python)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_iban&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iban&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;iban&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^A-Za-z0-9]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iban&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;rearranged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iban&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;iban&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isalpha&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rearranged&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validate_iban&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DE89370400440532013000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python integers are arbitrary-precision, so the bignum path just works — no chunking needed. The &lt;code&gt;re.sub&lt;/code&gt; also strips any stray spaces or punctuation left over from a copy-paste, so &lt;code&gt;"DE89 3704 0044 0532 0130 00"&lt;/code&gt; validates too. For production, add a per-country length check before this test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that trip people up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invisible junk from copy-paste.&lt;/strong&gt; Leading or trailing spaces, non-breaking spaces, soft hyphens, and line breaks pasted out of a PDF or email survive the copy and break validation. Stripping plain spaces isn't always enough — strip anything that isn't A-Z or 0-9.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O/0 and I/1/l confusion.&lt;/strong&gt; Reading an IBAN off a printed statement, people type the letter O for zero, or capital I / lowercase l for the digit 1. One wrong character flips the remainder off 1 — exactly what MOD-97 is built to catch. A wrong length or a mistyped country code (&lt;code&gt;DR&lt;/code&gt; for &lt;code&gt;DE&lt;/code&gt;) fails the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fast way (no math)
&lt;/h2&gt;

&lt;p&gt;Don't want to run the arithmetic? Paste the number into the &lt;a href="https://code-classify.com/iban-checker/" rel="noopener noreferrer"&gt;IBAN checker&lt;/a&gt;, which validates the MOD-97 checksum &lt;em&gt;and&lt;/em&gt; the per-country length right in your browser. Need to do this at scale — cleaning a payee list, or checking form input server-side? The &lt;a href="https://code-classify.com/api/" rel="noopener noreferrer"&gt;JSON API&lt;/a&gt; takes up to 100 IBANs per call and also handles GTIN, EU VAT, VIN, ISIN, and more. Same input, same output, no AI guessing.&lt;/p&gt;

</description>
      <category>python</category>
      <category>algorithms</category>
      <category>fintech</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Validate a GTIN / EAN-13 Barcode Check Digit (GS1 Mod-10, explained)</title>
      <dc:creator>rosario vitale</dc:creator>
      <pubDate>Sun, 05 Jul 2026 05:06:06 +0000</pubDate>
      <link>https://dev.to/rosariov25/how-to-validate-a-gtin-ean-13-barcode-check-digit-gs1-mod-10-explained-5boh</link>
      <guid>https://dev.to/rosariov25/how-to-validate-a-gtin-ean-13-barcode-check-digit-gs1-mod-10-explained-5boh</guid>
      <description>&lt;p&gt;If you have ever uploaded a product feed to Amazon, Google Merchant Center or Shopify and been rejected with "invalid GTIN," the culprit is almost always the &lt;strong&gt;check digit&lt;/strong&gt; — the last digit of the barcode. It is not random: it is calculated from all the other digits, and if it does not match, the platform rejects the code.&lt;/p&gt;

&lt;p&gt;Here is exactly how the check digit works, with a worked example you can follow by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a GTIN check digit?
&lt;/h2&gt;

&lt;p&gt;GTIN (Global Trade Item Number) is the umbrella term for the barcodes on products: &lt;strong&gt;GTIN-8, UPC-A (12 digits), EAN-13 (13 digits) and GTIN-14&lt;/strong&gt;. The final digit of every one of them is a &lt;strong&gt;check digit&lt;/strong&gt; computed with the &lt;strong&gt;GS1 Mod-10&lt;/strong&gt; algorithm. Its job is to catch typos: change one digit and the check digit almost always stops matching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GS1 Mod-10 algorithm, step by step
&lt;/h2&gt;

&lt;p&gt;Take all the digits &lt;strong&gt;except&lt;/strong&gt; the last (the check digit), then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Starting from the &lt;strong&gt;rightmost&lt;/strong&gt; of those digits, multiply every second digit by &lt;strong&gt;3&lt;/strong&gt; and the rest by &lt;strong&gt;1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add up all the results.&lt;/li&gt;
&lt;li&gt;Find what you must add to reach the next multiple of 10. That number (0-9) is the check digit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Formally: &lt;code&gt;check = (10 − (sum mod 10)) mod 10&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example — EAN-13 &lt;code&gt;400638133393?&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first 12 digits are &lt;code&gt;4 0 0 6 3 8 1 3 3 3 9 3&lt;/code&gt;. Working &lt;strong&gt;right to left&lt;/strong&gt;, alternate the weights ×3 and ×1:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;digit:   4  0  0  6  3  8  1  3  3  3  9  3
weight:  1  3  1  3  1  3  1  3  1  3  1  3
product: 4  0  0 18  3 24  1  9  3  9  9  9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Sum = 4+0+0+18+3+24+1+9+3+9+9+9 = &lt;strong&gt;89&lt;/strong&gt;.&lt;br&gt;
89 mod 10 = 9, so check = (10 − 9) mod 10 = &lt;strong&gt;1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The full valid barcode is therefore &lt;strong&gt;4006381333931&lt;/strong&gt;. If a feed lists &lt;code&gt;4006381333930&lt;/code&gt;, it is wrong — and that is exactly the kind of error that gets a listing rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  In code (Python)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gtin_check_digit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;gtin_check_digit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;400638133393&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# -&amp;gt; 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same algorithm works for UPC-A (12), EAN-13 (13), GTIN-8 and GTIN-14 — only the length changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that trip people up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Excel eats leading zeros.&lt;/strong&gt; A UPC like &lt;code&gt;036000291452&lt;/code&gt; becomes &lt;code&gt;36000291452&lt;/code&gt; the moment Excel treats the cell as a number, which changes the length and breaks validation. Format the column as &lt;strong&gt;Text&lt;/strong&gt; before pasting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong length.&lt;/strong&gt; UPC-A is 12 digits, EAN-13 is 13. Padding a UPC with a leading zero turns it into a valid EAN-13 — that is normal, not an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fast way (no math)
&lt;/h2&gt;

&lt;p&gt;If you just need to check or generate a check digit right now, paste the code into a free &lt;a href="https://code-classify.com/gtin-check-digit/" rel="noopener noreferrer"&gt;GTIN / UPC / EAN check-digit calculator&lt;/a&gt; — it validates GTIN-8 / UPC-A / EAN-13 / GTIN-14 in the browser and tells you the correct digit when it is wrong.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;bulk&lt;/strong&gt; work — validating a whole product feed, or wiring this into your own app — there is a deterministic &lt;a href="https://code-classify.com/api/" rel="noopener noreferrer"&gt;JSON API&lt;/a&gt; that checks up to 100 codes per call (and also handles IBAN, EU VAT, VIN, ISIN and more). Same input, same output, every time — no AI guessing.&lt;/p&gt;

</description>
      <category>barcode</category>
      <category>api</category>
      <category>webdev</category>
      <category>ecommerce</category>
    </item>
  </channel>
</rss>
