Short URL Code Length: The Math and the Real Numbers
You are building or picking a URL shortener and someone asks how many characters the code should be. Six? Seven? Here is the arithmetic, the collision math, and a dated measurement of what bit.ly, tinyurl.com, is.gd and the rest actually mint today.

Why the character count is a real design decision
You are writing a shortener, or reviewing one, and the question lands: how many characters should the code be? Four looks great in print and runs out fast. Ten never collides and nobody can read it over the phone. Most teams pick a number by copying whatever bit.ly appears to do, which is exactly how the internet ended up with a dozen blog posts that disagree about what bit.ly does.
This page is the arithmetic and the measurement. The code is everything after the last slash: the random back half in flyn.co/a7Kq2M, the alias you typed yourself, the thing a link alias actually is. Two numbers define it: how many characters long it is, and how many distinct symbols each character may take.
The code is an index, not a name. A generated code carries no meaning. It is a pointer into a row that stores the destination, which is why the destination can change later without the link changing, the property that makes editable links possible at all. Because it is a pointer, the only questions that matter are how many pointers you can express and how easily a human can carry one.
What actually gets shorter
Trimming one character from the code saves one character everywhere the link appears: a text message budget, a printed flyer, a radio read, a QR code's data payload. It is real, but it is small next to the domain. On flyn.co/a7Kq2M the domain and scheme are 16 of the 22 characters. If you want a shorter link, the domain is the bigger lever.
What breaks when you go too short
Short codes are dense codes. The shorter the code, the higher the fraction of the keyspace that is live at any moment, and the easier it is for a scanner to walk the space and find working links. This is the one part of the argument somebody has measured at scale: in Gone in Six Characters (2016), Georgiev and Shmatikov sampled 100,000,000 random six-character bit.ly tokens and found 42,229,055 live mappings, implying roughly 42 percent of that space was occupied, and on goo.gl/maps, then a five-character space, they scanned 63,970,000 tokens and got 23,965,718 live maps, a density of 37.5 percent. Four characters of base62 is 14,776,336 possibilities, a space about 3,800 times smaller than the six-character one they sampled. Their own throughput note is the honest counterweight: at the query rate they achieved, enumerating bit.ly's entire database would still have cost roughly 12.2 million compute hours, about 510,000 client-days, so it is rate limiting rather than arithmetic that makes a large space expensive to enumerate. That is a link safety question, not just an aesthetic one.
The alphabet decides more than the length does
Adding one character multiplies the keyspace by the alphabet size. Widening the alphabet from 36 to 64 symbols multiplies a six-character space by more than 31. Before you argue about six versus seven, settle which symbols are allowed.
RFC 3986 defines the unreserved set as letters, digits and the four marks hyphen, period, underscore and tilde (section 2.3). Those characters never need percent-encoding, which is why every serious code alphabet is drawn from them. Anything else has to survive autolinkers and email clients, and often does not, as anyone who has repaired a mangled link in the URL encoder knows.
base36, base58, base62, base64url
Four alphabets cover almost every shortener in production. base36 is digits plus lowercase letters, so it survives being uppercased. base58 drops the four glyphs humans confuse, 0, capital O, capital I and lowercase L, exactly as the base58 Internet-Draft describes. base62 is digits plus both cases. base64url adds hyphen and underscore, the URL-safe variant in RFC 4648 Table 2, and it is the default alphabet of nanoid, the id generator Flyn uses.
Case sensitivity is not optional
Scheme and host are case-insensitive; every other component of a URL is assumed case-sensitive unless the scheme says otherwise (RFC 3986, section 6.2.2.1). So on a base62 or base64url shortener, flyn.co/aB3xY9 and flyn.co/ab3xy9 are different links. That is free keyspace and a permanent support burden. It is also why is.gd states plainly in its API reference that its shortened URLs are case sensitive.
The glyphs that fail out loud
The ambiguity list is short and old: 0 against capital O, 1 against capital I against lowercase L, and in some fonts 5 against S. Crockford's base32 drops I, L, O and U entirely and publishes the reasoning per letter in its own write-up. If your codes get read aloud or typed off packaging, a reduced alphabet beats a shorter code.
Table 1: how many links each length holds
This is the table everyone half remembers. Multiply the alphabet size by itself once per character. Nothing subtle happens, but the magnitudes are worth staring at before you argue for an extra character.
| Code length | base36 | base58 | base62 | base64url |
|---|---|---|---|---|
| 4 | 1,679,616 | 11,316,496 | 14,776,336 | 16,777,216 |
| 5 | 60,466,176 | 656,356,768 | 916,132,832 | 1,073,741,824 |
| 6 | 2,176,782,336 | 38,068,692,544 | 56,800,235,584 | 68,719,476,736 |
| 7 | 78,364,164,096 | 2,207,984,167,552 | 3,521,614,606,208 | 4,398,046,511,104 |
| 8 | 2,821,109,907,456 | 128,063,081,718,016 | 218,340,105,584,896 | 281,474,976,710,656 |
| 9 | 101.6 trillion | 7.43 quadrillion | 13.5 quadrillion | 18.0 quadrillion |
| 10 | 3.66 quadrillion | 430.8 quadrillion | 839.3 quadrillion | 1.15 quintillion |
Read it as headroom, not as capacity
You will never fill a keyspace. The number that matters is what fraction is occupied. A tenth of a percent of a six-character base64url space is 68 million live links, more than most shorteners will ever hold, and at that fill level a fresh random code has about a one in a thousand chance of landing on a taken one.
Custom slugs share the same namespace as generated codes. Every vanity alias your users claim is one more code the generator can no longer mint, and the popular English words go first. That is a rounding error against 68 billion, but it is the reason a shortener needs one uniqueness check, not two.
When random codes collide: the birthday bound
Here is the part the combination tables leave out. If you generate codes at random, the first duplicate does not arrive when the space is full. It arrives near the square root of the space, and that is a much smaller number than people expect.
The formula, in one line. With a keyspace of N codes and k codes drawn at random, the chance that at least two match is roughly 1 minus e to the power of minus k squared over 2N. Set that to one half and solve: the 50 percent point sits at the square root of 2 times the natural log of 2 times N, which is about 1.177 times the square root of N. For six base64url characters, N is 68,719,476,736, its square root is exactly 262,144, and the 50 percent point is 308,651 links.
Table 2: when the first collision is likely
| Code length | Codes that exist (base64url) | Links before a 50 percent collision chance | At 1,000 links a day | At 100,000 a day | At 10 million a day |
|---|---|---|---|---|---|
| 5 | 1,073,741,824 | 38,581 | 39 days | 9 hours | 6 minutes |
| 6 | 68,719,476,736 | 308,651 | 309 days | 3 days | 44 minutes |
| 7 | 4,398,046,511,104 | 2,469,208 | 6.8 years | 25 days | 6 hours |
| 8 | 281,474,976,710,656 | 19,753,662 | 54 years | 198 days | 2 days |
| 9 | 18.0 quadrillion | 158,029,298 | 433 years | 4.3 years | 16 days |
What a collision actually costs
Almost nothing, if you built it right. The database refuses the insert, you mint another code and try again: one extra round trip on a fraction of a percent of writes. The birthday bound tells you how often that retry path runs, which is the difference between a rare branch and a hot one.
The birthday bound does not tell you when you run out of codes. It tells you when your retry path stops being theoretical and starts running in production.
The dangerous design is not a short code. It is a short code with no unique constraint on the code column. Without it, the second insert quietly overwrites the first and a live link starts pointing somewhere new. If you are building this yourself, add the unique index before you argue about length, and test the duplicate path deliberately with a redirect checker.
Table 3: the code lengths real shorteners use today
Every system-design tutorial asserts a number for bit.ly and none of them measured it. So we measured it, and dated it, because a generator can change and a claim from 2019 is worth nothing.
How this was measured
On 2026-09-10 we walked the public Common Crawl URL index for the August 2026 crawl, CC-MAIN-2026-34, and pulled every capture the index holds for each host: not a sample of one prefix, every page of the index for that host. We kept only single-segment paths made of letters, digits, hyphen and underscore, percent-decoded them, deduplicated, and took the length distribution. The modal length is the generated code length; the long tail is custom back halves, which share the namespace. Then we ran the whole thing three times, and that is where the honest part starts.
What reproduced and what did not
A repeat read of the same index does not return the same set, it returns a subset: every code in our smaller reads was also in our largest read, never the reverse, because the index is served from paged blocks ordered by path and a page can come back truncated. On the seven smaller hosts the three reads were identical. On the three largest they were not, and not by a little: bit.ly came back with 114,834 distinct codes on one read, 71,408 on another and 17,917 on a third. So the share of codes at a given length is not a stable figure, and a thinner read biases it upward: the same bit.ly query put 7-character codes at 69 percent, 73 percent and 82 percent of what it returned. That is the opposite of what an earlier version of this page claimed, and the earlier claim was wrong. What does hold on every read is the modal length and the share of modal-length codes carrying a capital, which stayed within 2 points on every host and within half a point on bit.ly, the host whose reads differed most in size. So the table prints the modal length, a floor on the distinct codes at that length, and the share as a range across reads. Earlier single reads of this same index, including the one first published here, put bit.ly at 71 percent, inside that spread, and tinyurl.com at 58 percent, above the 46 to 47 percent our three reads produced. If you cite one number off this page, cite the modal length.
| Host | Modal code length | Share at that length, across three reads | Distinct codes at that length | Alphabet at that length | Codes with a capital |
|---|---|---|---|---|---|
| shorturl.at | 5 | 100% | 88 | Mixed case and digits | 97% |
| is.gd | 6 | 95 to 100% | at least 13,910 | Mixed case and digits | 95% |
| v.gd | 6 | 98% | 943 | Mixed case and digits | 96% |
| tiny.cc | 6 | 33% | 121 | Mostly lowercase and digits | 10% |
| flyn.co | 6 | from source | not crawled | Mixed case, digits, underscore, hyphen | unfiltered |
| bit.ly | 7 | 69 to 82% | at least 79,603 | Mixed case and digits | 95% |
| buff.ly | 7 | 98 to 100% | 3,357 | Mixed case and digits | 97% |
| cutt.ly | 7 | 53 to 54% | 2,836 | Mixed case and digits | 98% |
| dub.sh | 7 | 36% | 193 | Mixed case and digits | 80% |
| tinyurl.com | 8 | 46 to 47% | at least 40,300 | Lowercase and digits | 1.2% |
| t.co | 10 | documented | not crawlable | not measured | not measured |
| ow.ly | 11 | 62% | 123 | Mixed case and digits | 100% |
What the table says
Seven mixed-case characters is the modern default, and bit.ly is the clearest case: 7 characters was the mode on all three reads, the most complete read holds 79,603 distinct 7-character bit.ly codes, and 95 percent of them carry at least one capital letter on every read. Six-character codes, the older generation still circulating, came to 4 percent of that read and between 2 and 4 percent of the thinner ones. tinyurl.com is the outlier at 8 characters, and the reason is the alphabet, not ambition: almost 99 percent of its 8-character codes contained no capital at all on both reads that completed, so it is drawing from roughly 36 symbols per position rather than 62, and six characters of that alphabet is only 2.2 billion. Dub is the one row a vendor confirms: its documentation says a link created without a key gets a random 7-character slug, and 7 was the mode we measured. X blocks crawlers from t.co, so that row is derived instead: its own counting rules say every URL in a post counts as 23 characters, and the fixed prefix takes 13 of them, leaving a 10-character code.
A low share is not a wrong mode, it is a busy namespace. cutt.ly sits near 54 percent because a third of its crawled codes are 8 characters, and dub.sh sits at 36 percent because Dub's users lean heavily on custom keys. Where the share is above 95 percent (is.gd, v.gd, buff.ly) essentially every link in the wild is a generated code. Where a range is printed the reads disagreed, so treat the modal length, not the share, as the finding.
What the table cannot say. This is what one crawler found, not a vendor statement. Custom back halves inflate the tail on every row. rb.gy and rebrand.ly returned no single-segment codes in this crawl at all; t.ly, bl.ink and short.gy returned too few to report; s.id and goo.su had codes spread across four lengths with no mode worth printing. And any provider can change its generator next week without telling anyone. Treat every row as what this host was minting when we looked, which is still more than any other page on this topic offers. For a feature-level comparison rather than a character count, the shortener roundup does that job.
What Flyn does, exactly
Here is our own answer, read out of the source rather than the marketing page, because a reference post that hides its own numbers is not a reference post.
Six characters, 64 symbols, nothing filtered
A link created in the dashboard or through the shorten endpoint gets a 6-character code from nanoid's default alphabet: A-Z, a-z, 0-9, underscore and hyphen, the base64url set. That is 68,719,476,736 possibilities. We do not strip ambiguous glyphs, so a generated code can contain 0 and capital O, or 1 and lowercase L, and it can start or end with a hyphen or an underscore. If a human is going to retype the link, do not accept the random code, set a slug.
The anonymous path uses seven and retries
Links minted without an account use a 7-character code and make four insert attempts in all, so three retries, on a uniqueness violation, generating a fresh code each time. The single-link API does not retry silently: a duplicate returns HTTP 409 so your client decides what to do. The bulk endpoint behaves differently again, because a batch insert is all or nothing: one colliding slug fails the whole batch, so it retries the batch once with fresh slugs. Three paths, three policies, and in every one of them the unique constraint on code plus domain is the backstop, not the generator.
Custom slug rules, in full.
- Characters: letters, digits, hyphen and underscore. Nothing else, no spaces, no percent-encoding.
- Length: up to 100 characters, which is far past the point of usefulness but stops nobody from writing a sentence.
- Uniqueness: per domain, so the same slug can live on your custom domain and on ours at the same time.
- Reserved words: 449 entries (447 unique) are refused case-insensitively, including every one-character slug, all ten digits, and route names such as login and pricing.
For anything printed, spoken or typed by hand, pick your own slug and keep it to lowercase letters and digits with no ambiguous pairs: no zero next to capital O, no 1 next to lowercase L. Draft candidates in the slug generator, then mint the batch through the bulk shortener so the whole campaign shares one naming pattern. Full field rules live in the links API reference.
Random, sequential or hash-derived
Length is half the decision. How you choose the code is the other half, and it changes what the code leaks about your business.
Sequential codes leak your volume
Counter-based codes (encode an auto-incrementing id into base62) are compact and never collide. They also publish your growth rate: mint two links a week apart, subtract the decoded ids, and you know how many links were created in between. The space is also dense, so walking it finds live links immediately.
Hash-derived codes leak duplicates. Hashing the destination and truncating gives you deduplication for free, plus a new problem: the same destination always yields the same code, so two customers pointing at one page share a link and an analytics bucket, and anyone can test whether a URL has ever been shortened on your service. If you want several distinct links for one destination, hashing is the wrong primitive.
Random codes, checked on insert
Random generation plus a unique constraint is what almost every production shortener converges on, including ours. It leaks no ordering and no volume, the sparse keyspace makes scanning uneconomic, and the retry path stays cheap. It demands one thing: a cryptographically decent random source, because a weak generator turns a 68 billion space into a much smaller one. Scanners are real enough that we ship a safety checker for links you receive.
Pick a length in five steps
If you are choosing a number today, this is the order that keeps you out of trouble. It takes about ten minutes and it survives contact with a growth curve.
- Fix the alphabet first. Mixed case and digits (62 or 64 symbols) for links that are clicked; lowercase and digits, or a reduced alphabet, for links that are typed or spoken.
- Estimate your five-year link count and multiply by ten. That number, not the theoretical keyspace, is what you are sizing for.
- Find the birthday point. Take the square root of the keyspace, multiply by 1.177, and check the answer against your link count from step 2. If they are close, add a character.
- Add the unique constraint and the retry. Then force a duplicate in a test so you know which error surfaces and what your client sees.
- Leave room for custom slugs. They share the namespace and they take the memorable words, so decide up front whether users can claim them and whether you reserve your own route names.
The sanity check
Six characters of base64url is the right default for anything short of a platform minting millions of links a day. Seven is right if you are that platform, or you want the retry path to stay theoretical for a decade. Below five, you are not building a shortener, you are building an enumeration target.
Gotchas that only show up after launch
Every one of these has bitten somebody. None of them are visible in the combination table.
Print, uppercase and the phone call
Designers uppercase headlines. A case-sensitive code set in all caps on a poster is a dead link, and nobody notices until the campaign is live. Use a case-insensitive alphabet for print or pick a slug that survives the treatment, and check the artwork with the link inspector before it ships. The same goes for codes behind a printed QR, where a reprint is the only fix.
Character budgets in messaging. In a text message every character is billed against a segment budget, so the six-versus-seven argument is not free there. It is still the domain that dominates the total, and the practical guidance for that channel lives in the SMS links guide.
QR density. A shorter URL is a lower-density QR, which is easier to scan at distance and more forgiving of a bad print. The effect is real but modest, and it is swamped by module size and quiet zone; the QR sizing guide has the numbers that actually matter.
The code is only as durable as the vendor
Length has nothing to do with whether the link resolves in five years. Google announced that goo.gl links with no recent activity would stop working after 25 August 2025 while active ones were preserved: the code is a tenancy, not a possession. Owning the domain is the only fix, which is the argument for branded links and for keeping an export of your click data.
Putting it into practice
The short version: settle the alphabet, size the length against the birthday point rather than the keyspace, put a unique constraint on the code column, and reserve your own route names before a customer claims one.
If you are using Flyn rather than building one
Generated codes are 6 characters on a signed-in link and 7 on an anonymous one, and neither number is configurable. What you control is whether a link uses a generated code at all: set a custom slug for anything a person will read, type or hear, and let the random code work everywhere else. Slugs and bulk creation are covered in creating your first link, and porting an existing back-half catalogue in the migration guide. Custom domains sit on Pro; codes and slugs do not.
Keep the measurement honest. Every number in the vendor table came from three reads of one public crawl index on 2026-09-10, and it can go stale the day a provider changes its generator. The modal lengths survived all three reads; the shares moved, so they are printed as ranges. Re-run it before you quote it in a spec. The math in Tables 1 and 2 does not go stale, and it is the part worth memorising. Nothing here is legal advice; it is engineering guidance. If you want to see a 6-character code arrive, create an account and mint one.
Frequently Asked Questions
How many characters is a bit.ly link?
Can two people ever get the same short code?
Is six characters enough for a URL shortener?
Why does TinyURL use eight characters when Bitly uses seven?
Should short codes exclude ambiguous characters like 0 and O?
Is a shorter domain worth more than a shorter code?
Do longer short codes make links more secure?
Does code length affect SEO or click-through rate?
Free tools for this
Three Flyn tools that pair well with the strategy in this article, all free, no signup needed.
UTM Builder
Build campaign-tracked URLs in seconds.
Broken Link Checker
Scan any page for dead links and 404s.
Open Graph Checker
Preview how URLs unfurl on social.
Keep reading
Three related deep-dives from the Flyn blog.
Postback URL Tracking: How S2S Conversion Tracking Works
17 min read

The Anatomy of a URL: Every Part Explained in Detail
15 min read

301 vs 302 Redirect: When to Use Each
14 min read
Ready to try Flyn?
Free plan includes 25 links/month, full analytics, and access to all 30+ free tools above. No credit card required.
Already a member? Log in

Karan Bhakuni is the founder of Flyn. He writes about branded links, click analytics, and the link-management tooling growth teams and creators actually need, drawn from building Flyn and reading a lot of user feedback.
Find these guides useful? Add Flyn as a preferred source so more of them show up in your Google results.