Skip to main content
We compare two approaches for generating short codes for a URL shortener: hashing vs. a global counter. With a counter-based approach you can guarantee uniqueness — the counter wins on collisions. However, the counter has security and concurrency drawbacks that deserve attention.
A dark-themed UI slide comparing "Hashing" (left) and "Counter" (right), with icons showing hashing is "short" (check) but fails on "retries as it fills" (red X), while the counter is "short" and "never repeats" (checks). A teal rounded button at the bottom reads "THE COUNTER WINS."
Security: enumeration risk
  • If short codes are direct encodings of a monotonically increasing counter (e.g., Base62-encoding of 1,000,000), an attacker who decodes a single code can enumerate adjacent IDs and discover links that were never shared.
  • Many Base62/decoder tools exist, making this a practical and trivial attack vector.
A dark-themed infographic titled "The First Weakness" showing an attacker using a shortlink (shortlink.io/4c92) that can be decoded to the ID 1,000,000. It also shows a counter increment and a nearby shortlink (shortlink.io/4c93) labeled "never shared."
Mitigation: reversible secret permutation (bijective shuffle)
  • Apply a reversible, secret permutation to the counter value before encoding. This preserves the counter’s uniqueness while hiding the sequence order from attackers.
  • The mapping must be bijective (one-to-one and onto) so every counter value maps to a unique shuffled value and can be reversed when needed.
Practical example (illustrative only — secure production use requires careful secret handling and vetted primitives):
The affine example above is educational. For production consider stronger, well-reviewed approaches (e.g., format-preserving encryption, Feistel networks, or block-cipher-based permutations) and treat the permutation parameters as secrets. Rotate and protect them like cryptographic keys.
Notes on the bijective-shuffle approach:
  • The transform is reversible — you can compute the inverse mapping when resolving short codes.
  • Different counter values map to different shuffled values, preserving uniqueness and avoiding collisions.
  • From an attacker’s perspective the codes look random and are not trivially enumerable.
  • Protect the permutation parameters (A, B, seed) as you would any cryptographic secret.
Concurrency: race condition with a shared counter
  • If two requests read the counter simultaneously and both use the same value before increment, both will generate the same short code. One mapping will overwrite or fail — producing duplicate short codes or lost links.
A dark-themed infographic showing two kodekloud URLs that converge on a single shared number ("1,000,042") with colored arrows and labels highlighting a weakness. Text boxes on the right show the same code repeated ("4c9K") and the caption "Two links, one code — one of them is lost."
Recommended fixes for race conditions
  • Use an atomic database sequence (Postgres sequences are safe under concurrency).
  • Use an atomic insert that returns the generated ID (e.g., SERIAL/IDENTITY with INSERT ... RETURNING).
  • Use optimistic inserts with a unique constraint on the short code and retry on conflict.
  • For very high write throughput, allocate ID ranges to multiple writers or use a distributed ID generator (Snowflake-style).
Table: concurrency options at a glance Concise design summary
  • One Postgres table maps short_code → long_url.
  • Create links by reading a global counter, shuffling the counter with a reversible secret function, then Base62-encoding the result.
  • Seven-character Base62 codes yield up to 62^7 (~3.5 trillion) unique values — adequate for many systems.
  • Index the short_code column for fast resolution.
A dark-themed slide illustrating a URL shortener design. It shows an index table of short_code → long_url, a Postgres note, and a counter → shuffle → base62 pipeline with labels like "7 characters" and "3.5 trillion codes."
Read-heavy considerations (CDN, cache, and edge)
  • Your URL shortener is typically read-heavy: ~1,000 reads per write is a common ratio. A direct app-server → Postgres lookup per redirect cannot scale under sudden spikes (e.g., a celebrity post).
  • Use caching and edge delivery to absorb read traffic and mitigate hot-key spikes.
A system diagram showing an app server communicating with a PostgreSQL database. The database contains a "urls" table mapping short_code values to long_url entries.
Caching and edge strategy checklist
  • Serve redirects via CDN edge caching or a reverse proxy with long-lived caches for popular links.
  • Cache short_code → long_url mappings in an in-memory cache (e.g., Redis) using a cache-aside pattern.
  • Set appropriate Cache-Control headers; 301 responses are cacheable by default, 302 may need explicit caching headers.
  • Use short TTLs for ordinary items and longer TTLs for hot items; pre-warm or pin extremely hot keys.
  • Apply rate limiting, DDoS protections, and CDN-level safeguards to protect origin services.
  • Keep redirect responses lightweight (HTTP 301/302) and avoid expensive logic on the hot path.
To avoid duplicate IDs under concurrency, prefer atomic database sequences (e.g., Postgres nextval) or a distributed ID generator. For read-heavy workloads, prioritize CDN/edge caching and a Redis cache to absorb traffic spikes.
Links and references Keywords: URL shortener, Base62, bijective mapping, counter, sequence, Postgres, CDN, Redis, cache-aside, Snowflake, read-heavy, hot keys.

Watch Video