bit.ly/my-link| Metric | Estimate |
| ----------------------- | ------------------------------------------------ |
| Write volume | 1 million new URLs/day = ~12 URLs/second |
| Read volume | 100 million redirects/day = ~1160/second |
| URL ratio | 100:1 read-to-write |
| Storage (5 years) | 1M × 365 × 5 = 1.8B URLs × 500 bytes ≈ **900GB** |
| New URLs/sec in 5 years | Maybe 100-500/sec (still manageable) |
create url:
post , api/v1/shorten
request :
long url
expired at
custom alias
response:
short url
created at
expires at
i want that url"
get , short url
redirect cache the browser
use 301 rather than 302 to avoid server load and make it faster
some decisions to consider:
during coding if i use hashmap there might be some collisions and due to the lack of uniqueness so i will use a base62 and also due to the incremental nature of this i might need to start at a random number
code: in js
import express from "express"
const app= express(json);
const char = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// to base62
function toBase62(num){
// base case
if(num=== 0 ) return "";
let result = "";
while( num > 0){
result= char[num%62] + result
num = Math.floor(num/62)
}
return result;
}
let nextId = 1;
const urlDatabase = {};
app.post('api/shorten/v1', async(req,res,next){
const longUrl= req.body._longUrl
const id= nextId++;
const shortCode = toBase62(id);
// Save
urlDatabase[shortCode] = longUrl;
res.json({
short_url: `bit.ly/${shortCode}`,
created_at: new Date().toISOString()
});
Database Design:
short code
long url
created at
expires at
click count
user_Id
// schema model prisma postgress and typescript
model URL_shortener {
id BigInt @id @default(autoincrement()),
shortCode String @unique @map("short_code") @db.VarChar(10)
longUrl String @map("long_url") @db.Text
createdAt DateTime @default(now()) @map("created_at)
expiresAt DateTime? @map("expires_at")
clickCount BigInt @default(0) @map("click_count")
userId Int? @map("user_id")
}
id and short_code? (Internal tracking vs user-facing)INDEX do? (Makes lookups fast)User: "Shorten https://example.com"
↓
Load Balancer → App Server
↓
1. Get unique ID from database/ID service
↓
2. Convert ID → base62 ("15FTG")
↓
3. Save to database: (15FTG → https://example.com)
↓
4. Return to user: "bit.ly/15FTG"
User: clicks "bit.ly/15FTG"
↓
Load Balancer → App Server
↓
1. Check Redis: "Do I know 15FTG?"
↓
┌─────────────────────────────────────┐
│ CACHE HIT (99% of time) │
│ Redis: "Yes! → https://example.com"│
│ Return: 301 redirect (~1ms) │
└─────────────────────────────────────┘
↓
CACHE MISS (1% of time)
Check database → Save to Redis → Return redirect (~10ms)
DB scaling
plain
Copy
Server A: "Give me ID" → Database: "1000"
Server B: "Give me ID" → Database: "1000" ← SAME! 😱
Both servers get 1000 → both create shortCode = "g8" → COLLISION
Table
| ApproachHow It WorksProsCons | |||
| Database Lock | SELECT FOR UPDATE | Simple | Slow, bottlenecks at scale |
| UUID/GUID | Random 128-bit number | No coordination needed | Too long for short URLs |
| ID Blocks | Pre-allocate ranges | Fast, no conflicts | Slightly complex |
| Snowflake | Time-based distributed ID | No central DB, sortable | Needs NTP sync |
plain
Copy
┌─────────────────────────────────────┐
│ ID Generator Service │
│ (Knows: next available = 1,000,000) │
└─────────────────────────────────────┘
↓ Hands out blocks of 1000
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Server 1│ │ Server 2│ │ Server 3│
│1M-1.001M│ │1.001M- │ │1.002M- │
│ │ │1.002M │ │1.003M │
└─────────┘ └─────────┘ └─────────┘
Implementation:
JavaScript
Copy
// ID Generator Service
class IDGenerator {
constructor() {
this.nextId = 1000000; // Start here
this.blockSize = 1000;
}
allocateBlock() {
const start = this.nextId;
this.nextId += this.blockSize;
return { start, end: start + this.blockSize - 1 };
}
}
// Each app server
class AppServer {
constructor() {
this.currentId = null;
this.maxId = null;
}
async getNextId() {
// Need more IDs?
if (this.currentId >= this.maxId) {
const block = await idGenerator.allocateBlock();
this.currentId = block.start;
this.maxId = block.end;
}
return this.currentId++;
}
}
Result: Servers generate IDs locally, no database calls, guaranteed unique.
Base62 sequential IDs: 1, 2, 3, 4... 10, 11... a, b, c...
Attack: bit.ly/100, bit.ly/101, bit.ly/102 → scrape all URLs!
Table
| ApproachHowResult | ||
| Start at offset | Begin at 1,000,000,000 instead of 1 | bit.ly/15FTGg8 not bit.ly/1 |
| Bit scrambling | Shuffle bits with secret key | 1 → 9847321, 2 → 1048293 |
| Random permutation | Feistel cipher on ID | Reversible, unpredictable |
| Crypto hash | HMAC(id, secret) | One-way, can't reverse |
JavaScript
Copy
const SECRET_KEY = 0x5DEECE66D; // Random seed
function scrambleId(id) {
// Start at offset (prevent tiny codes)
id += 1000000000;
// XOR with secret (simple scrambling)
id ^= SECRET_KEY;
// Mix bits (multiply by odd number, mod 2^53)
id = (id * 0xB) & 0x1FFFFFFFFFFFFF;
return id;
}
function unscrambleId(scrambled) {
// Reverse: find modular inverse of 0xB
const inverse = 0x2E8C2D41; // Pre-calculated
let id = (scrambled * inverse) & 0x1FFFFFFFFFFFFF;
id ^= SECRET_KEY;
id -= 1000000000;
return id;
}
// Test:
console.log(toBase62(scrambleId(1))); // "1aKz92" (not "1")
console.log(toBase62(scrambleId(2))); // "3mNp45" (not "2")
console.log(toBase62(scrambleId(3))); // "7xQr78" (not "3")
Properties: