Code Design for Business Systems — Deciding Product and Customer Codes, and Check Digits

· · Code Design, Check Digit, Business Systems, Database, C#, .NET, Excel, Decision Table, Design, Windows Development

“We’re migrating to a new system, so please decide the product code scheme.” — Whenever you build a business system or migrate an Excel ledger, this homework always comes up. And a code decided on the spur of the moment will, on average, outlive the system itself. The system may be replaced in ten years, but the customer numbers handed out to trading partners and the product codes printed on past forms live on.

At the same time, an astonishing amount of accumulated wisdom exists for code design. Large-scale numbering systems in operation — JAN codes, credit card numbers, Japan’s My Number — publish everything from their method of detecting input errors to how digits are allocated, along with the reasoning behind those design decisions. Using those as a foundation, this article sets out the rules for deciding product codes, customer codes, slip numbers, and the like in a business system, and the mechanism you can use (check digits).

1. The Bottom Line First

  • The code identifies; meaning is held as an attribute in the database. A “meaningful code” that embeds department, category, or fiscal year into its digits will inevitably break down under reorganisation or category change. My Number is designed as a number that carries no meaning.1
  • Attach a check digit to codes that people type, transcribe, or read aloud. It is known from measurement that the vast majority of input errors are “a single mistyped character” and “a swap of two adjacent characters”,2 and a check digit targets exactly those two.
  • Decide the character set from how it will be used. If phone, fax, or handwriting is involved, use digits only. If you need to save digits, use alphanumerics with the confusable characters (I, L, O and so on) removed (such as Crockford Base323).
  • Size the number of digits at around ten times the future record count, and if you use leading zeros, treat the code as a string in every system. The moment Excel interprets it as a number it drops leading zeros and rounds digits from the 16th onward to 0.4
  • Never reuse a code once it has been used, even if it becomes vacant. Numbers left in past forms, logs, and trading partners’ systems would end up pointing at a different thing.
  • If you are putting barcodes on products and sending them into distribution, use JAN codes (the GS1 standard) rather than your own codes.5

2. Should the Code Carry Meaning? — The First and Biggest Fork in the Road

The first thing to decide in code design is neither the number of digits nor the character set, but “should the code carry meaning?”

A common design goes like this: “Product codes are 8 digits. The first 2 are the department, the next 3 the category, the remaining 3 a serial number.” It looks tidy the moment it is decided, but within a few years you get this:

  • A reorganisation merges departments, and products carrying the old department code are left in limbo. Renumber them and you can no longer reconcile against past forms; leave them and exceptions where “the code’s department differs from the actual department” proliferate in the master data.
  • Products that span categories appear (something that is both food and a general good), and you need an internal rule for which code to assign.
  • One category grows so much that its 3-digit serial runs out, and an ad-hoc practice begins of “for this category only, exceptionally, borrow a free number from another department.”

The cause is clear: organisations and categories change, yet they were burned into the code on the assumption that they would not. So the principle is the opposite. Make the code a symbol that does nothing but point uniquely at a thing, and hold attributes such as department and category in database columns. As attributes they can change as much as they like — one UPDATE and the code is untouched.

The national numbering systems are built on this principle too. My Number is an 11-digit number carrying no meaning, generated by converting the resident record code “by a method to which no intent is applied,” plus one check digit.1 Carrying no meaning keeps individual attributes from being inferred from the number, but it also means the number does not change when an attribute changes (a move, a change of surname).

That said, in practice the wish that “at least the first character should tell you whether it’s a customer or a supplier” is persistent, so here it is as a decision table.

Approach Example Strengths Where it breaks When it fits
Fully meaningful code 02-104-317 (department-category-serial) Read it and you know the attributes Renumbering on reorganisation, category change, or partial digit exhaustion Only when the population neither grows nor shrinks and categories are fixed by regulation
Meaningless serial 10000317 Nothing breaks it, whatever changes. Numbering is simple People can read nothing from the code Operations premised on a system that always shows attributes alongside on screens and forms
Hybrid C-0317-5 (1-character type + serial + check digit) At least prevents confusing types. Minimal breakage surface Redefinition of the type (rare) If in doubt, this. Keep the type as coarse as “customer / supplier / product”

The recommendation is the bottom two rows. Take it that the more meaning you embed, the more seeds of breakage you plant.

3. Character Set and Length — Information Efficiency Versus Readability

Next come the character set and the number of digits. This is pure arithmetic: the more character types available per position, the more things you can represent in the same number of digits.

Character set Count representable in 6 digits Resistance to reading aloud / handwriting
Digits only (10 types) 1 million ◎ Strong for phone, fax, and handwriting
32 types: uppercase letters + digits with confusable characters removed (Crockford Base323) About 1.07 billion ○ Misreading is addressed, but reading aloud is worse than digits
Uppercase letters + digits (36 types) About 2.18 billion △ Misreading of 0 vs O and 1 vs I is inevitable

There are two criteria:

  • Will this code be read aloud over the phone, or handwritten or faxed? If even one applies, digits only is recommended. The moment letters are mixed in, you incur the cost of “was that an I or a one?” checks and the risk of misreading.
  • Would digits alone make the code too long? Only when you need to compress the length — because the population exceeds tens of millions, for example — should you consider alphanumerics. Even then, use a pre-designed alphabet such as Crockford Base32, which excludes I, L, and O (and U, to avoid accidental obscenities), rather than the raw 36 types. That specification is thorough about misreading: on decoding it accepts lowercase and interprets i and l as 1 and o as 0.3

Size the number of digits by working back not from “the current record count” but from “the count reachable over the 20 to 30 years the system and its forms will live,” and then add one digit of headroom. The fear of digit exhaustion is covered in Section 8, but one extra digit is a trivial price to avoid inflicting on yourself the kind of wholesale rework that Japan’s postal codes required when they went from 5 digits to 7 (1998).

One more thing that quietly helps is separators. Just as credit card numbers are printed in groups of four, humans can only handle long digit strings accurately in chunks of three or four. If you put codes of 8 digits or more on forms or screens, display them separated, as in 1234-5678. But the principle is that the separator is not part of the data; it is added at display time (Section 6).

4. Check Digits — Letting the Code Itself Detect Input Errors

4.1 Input Errors Come in Two Kinds

A check digit is a single digit appended to the end (or the beginning) of a code, computable from the other digits, so that an error in an entered code can be detected on the spot. My Number’s check digit likewise has its purpose written into the cabinet order: “for the purpose of confirming that there is no error when entering it into a computer.”1

Which formula is good is determined by the mistakes humans actually make. According to Verhoeff’s classic study analysing error data from systems such as the Dutch postal giro, 60 to 95% of errors are a mistyping of a single digit (single error) — overwhelmingly the largest class. Next come two-digit errors at 10 to 20%, most of which are two adjacent digits, and in particular an abba swap (transposition). Every other class (the aabb type, jump transpositions, and so on) accounts for only about 0.5 to 1.5% each.2

In other words, the performance of a check digit scheme can effectively be judged on two points: “how many single-digit errors can it detect” and “how many adjacent transpositions can it detect.”

4.2 Schemes in Actual Use and Their Detection Power (Decision Table)

Scheme Where it is used Single-digit errors Adjacent transpositions Characteristics
Modulus 10 weight 3-1 GS1 standards such as JAN codes, ISBN-136 All detected Misses pairs differing by 5 (0550, 1661, and so on) For barcode operations, effectively the only choice
Luhn (modulus 10) Credit card numbers7 All detected Misses only the single pair 0990 Simplest to implement. The default candidate for your own codes
Modulus 11 (weighted) My Number8, old ISBN Nearly all detected Nearly all detected High detection power, but has the “balancing the remainder” problem (see below)
Modulus 9 (weighted) Corporate Number9 Misses 09 Misses the 09 pair Because it divides by 9, it cannot distinguish 0 from 9
Damm / Verhoeff Academic schemes All detected All detected Implemented by table lookup (Damm10) or group operations (Verhoeff2)

A few notes:

  • The JAN scheme (modulus 10 weight 3-1) multiplies the digits alternately by 3 and by 1 starting from the rightmost digit, sums them, and takes the units digit of “10 − (the sum modulo 10)” as the check digit.6 A transposition leaves the sum unchanged when the difference between the ×3 and ×1 weights, multiplied by the difference between the digits, is a multiple of 10 — that is, when the two digits differ by exactly 5, and only that pattern goes undetected.
  • Luhn is the scheme H. P. Luhn of IBM filed a patent for in 1954,7 doubling every other digit and subtracting 9 when the result exceeds 9 before summing. The only adjacent transposition it misses is the single pair 0990. It strikes a good balance between simplicity of implementation and detection power, and if you are newly adopting one for your own codes, this is where to start.
  • My Number’s modulus 11 has high theoretical detection power thanks to 11 being prime, but there is a folding rule that makes the check digit uniformly 0 when the remainder is 0 or 1,8 and errors that move between those two classes alone cannot be detected. The old ISBN (ISBN-10) solved this with the same modulus 11 by “writing remainder 10 as X”, but then took on the operational nuisance of “an X appearing in a position that is supposed to be a digit.” If you adopt a modulus 11 family scheme, you should know that the “how do we squeeze 11 possible remainders into 10 digits” problem always comes with it.
  • The Corporate Number’s modulus 9 is an unusual design that puts the check digit at the front,11 but because it uses the remainder modulo 9, 0 and 9 are congruent, so mistyping or swapping those two passes straight through.
  • The Damm and Verhoeff schemes detect all single-digit errors and all adjacent transpositions. Verhoeff was the first to construct a decimal code detecting all single errors and all adjacent transpositions,2 and Damm gave a simpler construction that only requires looking up a single quasigroup operation table.10 If detection power is the top priority, these are the ones; but as a countermeasure against input errors in a business system, Luhn or the JAN scheme is practically sufficient.

4.3 Codes That Need a Check Digit, and Codes That Don’t

The criterion is “does it pass through human hands and eyes?” A product code keyed in from a paper order form, a membership number given over the phone, a slip number handwritten on site — all worth attaching one to. Conversely, internal IDs that only flow through system-to-system integration, and codes that can only be entered by selection on a screen, do not need one. If there is no source of mistakes (a human), there is nothing to detect.

5. Implementation Examples in C#

The main schemes can each be implemented in a few to a dozen or so lines. The only thing to watch is that you should always receive the code as a string (the reason is in Section 6).

First the JAN scheme (GTIN-13). The check digit is calculated from the 12-digit body.6

public static int Gtin13CheckDigit(string body12)
{
    if (body12.Length != 12 || !body12.All(char.IsAsciiDigit))
        throw new ArgumentException("Specify 12 digits", nameof(body12));

    int sum = 0;
    for (int i = 0; i < 12; i++)
    {
        int digit = body12[11 - i] - '0';        // count from the right-hand end
        sum += (i % 2 == 0) ? digit * 3 : digit; // rightmost is ×3, alternating thereafter
    }
    return (10 - sum % 10) % 10;
}

Next, Luhn. If you are attaching one to your own customer codes or membership numbers, these two are all you need.

public static int LuhnCheckDigit(string body)
{
    int sum = 0;
    for (int i = 0; i < body.Length; i++)
    {
        int digit = body[body.Length - 1 - i] - '0';
        if (i % 2 == 0)              // double every other digit, starting next to the check digit
        {
            digit *= 2;
            if (digit > 9) digit -= 9;
        }
        sum += digit;
    }
    return (10 - sum % 10) % 10;
}

public static bool IsValidLuhn(string code) =>
    code.Length >= 2 && code.All(char.IsAsciiDigit) &&
    LuhnCheckDigit(code[..^1]) == code[^1] - '0';

Here is also validation of the Corporate Number, usable for input checking against a customer/supplier master. It is a direct transcription of the formula in the ministerial ordinance9 (the first digit is the check digit, the following 12 digits are the base number).

public static bool IsValidCorporateNumber(string code)
{
    if (code.Length != 13 || !code.All(char.IsAsciiDigit)) return false;

    int sum = 0;
    for (int n = 1; n <= 12; n++)
    {
        int p = code[13 - n] - '0';          // the least significant digit of the base number is n=1
        sum += p * (n % 2 == 0 ? 2 : 1);     // odd positions ×1, even positions ×2
    }
    return 9 - sum % 9 == code[0] - '0';
}

Checking against the example in the National Tax Agency’s material (company registration number 700110005901 → Corporate Number 8700110005901): the sum of the odd positions 11 + the sum of the even positions 13 × 2 = 37, 37 divided by 9 leaves remainder 1, and 9 − 1 = 8, which matches.11

There is a further refinement in how you use this on the input screen. Rather than just saying “invalid code” when the check digit does not match, go as far as looking up the master and echoing back the name (“1000317: Acme Co., Ltd. — is that correct?”). That way, mistyped input that slips past the check digit (where the user typed a different code that happens to exist) can also be caught by human eyes.

6. Pitfalls You Will Definitely Hit in the Field

Even with a good code scheme, there are standard patterns where implementation and operations ruin it.

  • Excel’s dropped leading zeros and 15-digit rounding. When Excel interprets a cell’s contents as a number it removes leading zeros, and because the significant precision of a number is 15 digits, it replaces digits from the 16th onward with 0.4 Customer code 00123 becomes 123, and credit-card-length numbers get their tail turned into 0. For codes in a system with CSV integration, build treating them as strings on every path (specifying the text type in Power Query, specifying column types on the importing side) into your operating procedures from the outset.
  • Storing them in a numeric column in the database. Leading zeros vanish just as in Excel, and a BETWEEN intended to “filter by code range” drags in codes of different lengths. A code is not something you compute with, so a fixed-length string type is the principle. Design the sort order as a string too (fix the length and string sort equals numeric sort).
  • Including hyphens in the data. Once 1234-5678 and 12345678 start coexisting as separate records, it’s over. Store the bare code, add separators at display time. On input, strip hyphens and spaces before validating.
  • Inconsistent letter case. If you use letters, normalise to uppercase before storing and enforce consistency yourself rather than relying on the collation.
  • Reusing vacated numbers. “Customer code 1000317 has cancelled, so let’s give it to a new customer” is out of the question. Past invoices, logs, and trading partners’ systems still hold the old association, and an audit or a fault investigation will end up pointing at the wrong party. Codes should be permanently retired as a matter of principle.

7. Codes You Must Not Decide Yourself — Riding an Existing Standard

Codes that stay inside the company can be designed freely, but if you print barcodes on products and send them outside (retail, e-commerce, logistics), use JAN codes (GTIN) rather than your own. A JAN code is assigned under a GS1 company prefix licensed to you; you cannot simply decide one yourself.5 The method for calculating the check digit is defined by the standard as well.6

The design point here is not to force your internal code and the JAN code into one. Because the same product can have different JANs for different pack sizes, and a JAN can change with a specification change, the standard practice is to hold “internal product code (effectively the primary key, numbered by you)” and “JAN code (an attribute, possibly multiple)” as separate columns in the product master. Here too, the principle “the code identifies; meaning (the mapping to an external standard) is an attribute” applies directly.

8. The Lifespan of a Code Scheme, and Migration

However carefully you design it, a code scheme eventually reaches the end of its life. The typical cause is digit exhaustion. When the ceiling of the serial number comes into view, the only option is “add digits” — but the digit count is burned not only into the master’s column definitions but into form layouts, barcode print widths, the file specifications for integration with trading partners, and even into the trading partners’ own systems. You end up carrying out a migration on the scale of the Japanese postal code’s move to 7 digits (1998), across your own company and all of your trading partners.

That is exactly why the “one digit of headroom” from Section 3 pays off — but when migration does become necessary, there are three principles:

  • Build an old-to-new mapping master, and make both codes searchable during the transition period. Enquiries from trading partners will come in with the old code.
  • If you separated the internal key from the code, migration stays confined to the display layer and the master. If you used the business code itself as the database primary key, the foreign keys of every table go down with it. For new designs, we recommend separating the internal key (auto-numbered) from the display code.
  • Deliver schema changes as version-controlled migrations. How to reliably propagate a change in a code column’s length to every customer and every environment is covered in the article on database schema migration.

9. Summary

  • The first principle of code design is “the code identifies, meaning is an attribute”. Burn departments or categories into the code and changes in organisation and classification become breakage in the code. That My Number is a meaningless number is no accident.1
  • Character set and length are a trade-off between information efficiency and readability. If it will be read aloud or handwritten, digits only; if you want to compress the length, use an alphabet designed against misreading (Crockford Base323). Size it at ten times the future record count, plus one digit.
  • The reality of input errors is “single-digit errors 60 to 90%, adjacent transpositions most of the remainder.”2 Attach a check digit to codes that pass through human hands; if in doubt about the scheme, use Luhn, and if it goes on a barcode, use the GS1 standard.6 The “balancing the remainder” problem of the modulus 11 family, and the fact that the Corporate Number’s modulus 9 cannot distinguish 0 from 9, are properties worth knowing when selecting a scheme.
  • Implementation should be strings throughout. Excel’s dropped leading zeros and 15-digit rounding,4 storage in numeric columns, hyphens creeping into the data, and reuse of vacated numbers are the four great field accidents.
  • Product codes that go outside should ride JAN (GS1) and be held in a separate column from the internal code. In preparation for a digit-exhaustion migration, keep the internal key and the display code separate.

A code scheme is a de facto external specification: once it has been handed out, the cost of fixing it later is orders of magnitude greater. When codes come up during requirements definition for a new system, run through the checklist in this article before you get to screens and features.

KomuraSoft LLC handles code scheme and master design for new and replacement business systems, investigation of digit exhaustion and duplication in existing code schemes together with migration planning, and implementation of input validation (check digits and master lookups). As with the comparison of check digit schemes in this article, we also take on consulting in the mathematical consulting area, where “which scheme detects which mistakes, and how well” is evaluated with formulas and turned into design decisions.

References

  1. Order for Enforcement of the Act on the Use of Numbers to Identify a Specific Individual in Administrative Procedures (Cabinet Order No. 155 of 2014), Article 6. On the number to be used as the Individual Number consisting of an 11-digit number obtained by converting the resident record code, generated by a method to which no intent is applied, followed by a single check digit (an integer from 0 to 9 calculated for the purpose of confirming that there is no error when the Individual Number is entered into a computer).  2 3 4

  2. J. Verhoeff, Error Detecting Decimal Codes, Mathematical Centre Tracts 29, Mathematisch Centrum, Amsterdam. On the frequencies of error classes based on analysis of error samples from real systems (single-digit errors the largest class at 60 to 95%, two-digit errors at 10 to 20% of which most are transpositions of adjacent digits, and minority classes such as twin errors at 0.5 to 1.5% each), and on the author’s construction of a decimal code detecting all single-digit errors and all adjacent transpositions (transposition in that work refers to the swapping of adjacent digits).  2 3 4 5

  3. Douglas Crockford, Base 32. On the 32-character alphabet excluding I and L (confusable with 1) and O (confusable with 0), plus U to avoid accidental obscenities; on decoding accepting either case and treating i and l as 1 and o as 0; and on the check symbol mechanism using mod 37.  2 3 4

  4. Microsoft Support, Keep leading zeros and large numbers. On the significant precision of numbers in Excel being at most 15 digits, so that in numbers of 16 digits or more such as credit card numbers the part beyond 15 digits is changed to 0, on leading zeros being removed, and on the workaround of treating the column as text.  2 3

  5. GS1 Japan, GS1 company prefixes and GTIN (JAN codes). On the fact that using JAN codes requires a registration procedure to be licensed a GS1 company prefix.  2

  6. GS1 Japan, How to calculate the check digit. On the check digit of GTIN-13 (the standard JAN code type) being calculated by taking the sum of the digits multiplied alternately by 3 and 1 from the rightmost digit and then “subtracting (the sum modulo 10) from 10”.  2 3 4 5

  7. H. P. Luhn, US Patent 2,950,048 “Computer for Verifying Numbers” (filed 1954, granted 1960). On the scheme of appending a check digit to the right-hand end of the original number and verifying the number by a cross-addition using substitute digits (the digit sum of the doubled digit).  2

  8. Order on the Individual Number, Individual Number Card, and Provision of Specific Personal Information under the Act on the Use of Numbers to Identify a Specific Individual in Administrative Procedures (Ministry of Internal Affairs and Communications Order No. 85 of 2014), Article 5. On the formula for the check digit (multiply the digit Pn at position n counted from the least significant of the 11 digits other than the check digit by a weight Qn of n+1 for 1≦n≦6 and n−5 for 7≦n≦11, sum, divide by 11, and subtract the remainder from 11; if the remainder is 1 or less, use 0).  2

  9. Ministerial Ordinance on the Designation of Corporate Numbers (Ministry of Finance Order No. 70 of 2014), Article 2. On the formula for the Corporate Number’s check digit (multiply the digit Pn at position n counted from the least significant of the base number by a weight Qn of 1 when n is odd and 2 when n is even, sum, divide by 9, and subtract the remainder from 9).  2

  10. H. M. Damm, Totally anti-symmetric quasigroups for all orders n≠2,6, Discrete Mathematics, Vol. 307, 2007. On the existence, for all orders except 2 and 6, of the totally anti-symmetric quasigroups that underpin check digit schemes detecting all single-digit errors and adjacent transpositions.  2

  11. National Tax Agency, Calculating the check digit. On the Corporate Number consisting of a 12-digit base number preceded by a single check digit, and on the worked example calculating check digit 8 from company registration number 700110005901.  2

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

Which codes should have a check digit?
Codes that people type, transcribe, or read aloud. Typical examples are a product code keyed in from a paper form, a membership number given over the phone, or a slip number written by hand. Conversely, internal IDs that only ever flow between systems (such as database primary keys) do not need one. Codes that never pass through human hands do not suffer typing errors, and the purpose of a check digit is to detect input errors. Japan's My Number check digit is likewise defined in law with the stated purpose of "confirming that there is no error when it is entered into a computer."
Is it acceptable to give a product code meaning, such as a department or a category?
The principle is: "the code identifies, and meaning is held as an attribute in the database." If you embed departments, categories, or fiscal years into the digits of a code, every reorganisation or category change forces a renumbering, and reconciliation against past forms and numbers already given to trading partners breaks. If people really do need to tell things apart at a glance, keep it to a single-character prefix indicating the type and make everything else a serial number — that is the practical compromise.
Can we retrofit a check digit onto an existing code scheme?
Technically yes, but because it adds a digit it affects the master data, every form, data integration with trading partners, and printed material. In practice it becomes a code-scheme migration project, so it is realistic to align it with a point where you are overhauling the scheme anyway, such as when dealing with digit exhaustion. As a stopgap until then, simply performing a master existence check on the input screen (verifying that the entered code actually exists) and echoing back the name will already reduce the practical damage from mistyped input considerably.
Can we use a UUID or ULID as a business code?
They are fine as internal database keys, but they are not suited to a "display code" that people read aloud or transcribe. A UUID is 36 characters — far too long to survive being passed over the phone or by fax. If you use a two-layer structure with an internal key (a UUID or auto-increment number) and a separate display code shown to people (a short serial number plus a check digit), you can satisfy both requirements. Even if you later have to change the display code scheme, a stable internal key keeps the blast radius confined to the display layer.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog