Back to Blog

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.

Karan Bhakuni
Karan Bhakuni
Founder, Flyn
TechnicalSep 10, 202617 min readUpdated Sep 10, 2026
Short URL Code Length: The Math and the Real Numbers

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.

A short link split into a fixed 16-character domain and a six-character code, with the alphabet size and the resulting keyspace of 68,719,476,736Tap to enlarge
Only the code is yours to size. The domain length is a separate decision with separate tradeoffs.

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.

Reference card comparing base36, base58, base62 and base64url by characters, excluded glyphs, case sensitivity and the number of six-character combinationsTap to enlarge
Four alphabets, four keyspaces, four different answers to what happens when a person retypes the code.

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.

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.

Log-scale bars showing links minted before a 50 percent chance of a collision for code lengths 5 through 9 on a 64-character alphabetTap to enlarge
Each extra character multiplies the safe run by 8, not by 64, because the birthday bound tracks the square root of the keyspace.

Table 2: when the first collision is likely

Code lengthCodes that exist (base64url)Links before a 50 percent collision chanceAt 1,000 links a dayAt 100,000 a dayAt 10 million a day
51,073,741,82438,58139 days9 hours6 minutes
668,719,476,736308,651309 days3 days44 minutes
74,398,046,511,1042,469,2086.8 years25 days6 hours
8281,474,976,710,65619,753,66254 years198 days2 days
918.0 quadrillion158,029,298433 years4.3 years16 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.
Watch out

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.

Horizontal bars ranking twelve shortener hosts by their most common code length, from five characters on shorturl.at to eleven on ow.lyTap to enlarge
Modal code length per host, from every short URL the August 2026 Common Crawl index held for that host, read three times on 2026-09-10.
HostModal code lengthShare at that length, across three readsDistinct codes at that lengthAlphabet at that lengthCodes with a capital
shorturl.at5100%88Mixed case and digits97%
is.gd695 to 100%at least 13,910Mixed case and digits95%
v.gd698%943Mixed case and digits96%
tiny.cc633%121Mostly lowercase and digits10%
flyn.co6from sourcenot crawledMixed case, digits, underscore, hyphenunfiltered
bit.ly769 to 82%at least 79,603Mixed case and digits95%
buff.ly798 to 100%3,357Mixed case and digits97%
cutt.ly753 to 54%2,836Mixed case and digits98%
dub.sh736%193Mixed case and digits80%
tinyurl.com846 to 47%at least 40,300Lowercase and digits1.2%
t.co10documentednot crawlablenot measurednot measured
ow.ly1162%123Mixed case and digits100%

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.

Reading the share column

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.

Flow diagram of three Flyn creation paths: a signed-in link gets a six-character code and returns 409 on a duplicate, an anonymous link gets seven characters and makes four insert attempts, three of them retries, and a bulk batch is all or nothingTap to enlarge
Three creation paths, three duplicate policies. Only the unique index on code plus domain is common to all of them.

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.
Pro tip

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.

Cards comparing random, sequential and hash-derived code generation across collision behavior, what the code leaks, keyspace density and whether one destination can hold two linksTap to enlarge
The length is identical across all three. What changes is what an outsider can read off the code.

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.

Decision tree with three questions routing to a custom slug, a six-character code, a seven-character code or eight characters, with the birthday point behind each thresholdTap to enlarge
Thresholds are the birthday points from Table 2, so every branch is a number you can check rather than a preference.
  1. 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.
  2. Estimate your five-year link count and multiply by ten. That number, not the theoretical keyspace, is what you are sizing for.
  3. 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.
  4. 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.
  5. 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.

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?
Seven characters. We read every bit.ly capture in the August 2026 Common Crawl index three times on 10 September 2026, and 7 characters was the most common length on all three reads. The most complete read returned 114,834 distinct single-segment codes, 79,603 of them exactly 7 characters, with 95 percent of those carrying at least one capital letter. About 4 percent were 6 characters, the older generation of Bitly codes still circulating; the rest were custom back halves of varying length. The share is not a stable figure, because a repeat read of this index returns a subset rather than the same set: the three reads put 7 characters at 69, 73 and 82 percent of what they returned. With the https prefix and the domain, a default bit.ly link comes to 22 characters. Bitly does not publish its generated code length, so treat this as a dated measurement of what was in the wild, not a vendor statement.
Can two people ever get the same short code?
Not if the service is built correctly. Random generation will eventually produce a duplicate, and the birthday bound says that for six characters of a 64-symbol alphabet a 50 percent chance of a duplicate arrives after roughly 308,651 links. What prevents a shared link is the unique constraint on the code column: the second insert is rejected, and the service either mints a new code and retries or returns an error. On Flyn the anonymous path makes four insert attempts in all, so three retries with a fresh code, and the authenticated API returns HTTP 409 so the calling client can decide.
Is six characters enough for a URL shortener?
For almost everyone, yes. Six characters from a 64-symbol alphabet is 68,719,476,736 combinations, and the practical limit is not exhaustion but the collision retry rate. At a hundred million stored links, a freshly generated six-character code has about a 0.15 percent chance of hitting a taken one, which is a cheap retry. If you are minting millions of links a day, or you want the retry branch to stay theoretical for a decade, use seven. Below five characters the keyspace becomes dense enough to enumerate, which is a security problem rather than a capacity one.
Why does TinyURL use eight characters when Bitly uses seven?
Because they use different alphabets. In the 10 September 2026 measurement, almost 99 percent of tinyurl.com's 8-character codes contained no capital letter at all, so it is drawing from roughly 36 symbols per position, while 95 percent of bit.ly's 7-character codes did contain a capital, which puts it at 62. Six characters of base36 is 2.18 billion combinations against 56.8 billion for base62, so a lowercase-only service needs roughly one extra character to reach comparable headroom. The upside of the lowercase alphabet is real: the link survives being uppercased in print or dictated over a phone without changing which link it points to.
Should short codes exclude ambiguous characters like 0 and O?
It depends entirely on how the link travels. If people only ever click it, ambiguity costs nothing and the wider alphabet buys keyspace. If people type it from a poster, read it aloud, or dictate it to support staff, then 0 against capital O and 1 against capital I against lowercase L will generate failed loads. That is why base58 omits those four glyphs and Crockford base32 omits I, L, O and U. Flyn does not filter them out of generated codes, so for print or voice, set a custom slug in lowercase letters and digits instead.
Is a shorter domain worth more than a shorter code?
Usually yes, because the domain is repeated in every character count and the code is not. On a link like flyn.co/a7Kq2M, the scheme and domain are 16 of 22 characters, so moving from a seven-character code to six saves one character while moving from a fifteen-character domain to a seven-character one saves eight. Choosing between raw brevity and a recognisable branded domain is a separate decision, weighed in the shortener roundup at /blog/best-url-shorteners and in the guide to choosing a branded short domain.
Do longer short codes make links more secure?
Longer codes make a link harder to find by guessing, but they never make it secret. Anyone who has the URL can open it, previews and proxies can fetch it, and it can be forwarded. The canonical demonstration is Georgiev and Shmatikov, Gone in Six Characters (arXiv:1604.02734, 2016), which brute-force scanned the 5- and 6-character token spaces behind cloud sharing links: 63,970,000 tokens scanned on goo.gl/maps returned 23,965,718 live maps, and more than 7 percent of the OneDrive and Google Drive accounts reached that way contained world-writable folders. A ten-character random code from a 64-symbol alphabet is effectively unguessable, which is enough to keep scanners away, but if the content genuinely must be restricted then the control is a password or an expiry, not a longer string. Flyn offers both as link settings, with password links and click limits on the paid plans.
Does code length affect SEO or click-through rate?
Code length has no direct effect on search rankings, because a short link is a redirect and the destination page is what gets indexed. Click-through is a different story, but the evidence points at the domain and the readability of the path, not at the character count of a random code. A recognisable branded domain with a meaningful slug reads as trustworthy; a random six-character code on an unknown domain reads as a risk. If click-through is what you are optimising, spend the effort on the domain and a descriptive custom slug rather than on shaving one character off the generated code.

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
Karan Bhakuni· Founder, Flyn

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.