Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
417: Building a URL Shortener Backend
When people first try to build a URL shortener, they almost always start with the same approach: they generate a random 6-character string using random.choices(), check if it's already in the database, and if it is, they just loop and try again. It feels intuitive. It feels "random."
Random Strings Aren't Unique Enough
Here is why that approach is a trap. Let's say you're using a character set of 62 characters (A-Z, a-z, 0-9). With a 6-character string, you have about 56 billion possibilities. That sounds huge, right? But because of the Birthday Paradox, the probability of a collision happens much faster than your intuition tells you. Once you hit a few million URLs, your "generate and check" loop starts hitting existing keys more often, slowing down your write operations exactly when your app is becoming successful.
# The "naive" way that eventually slows down
import random
import string
def generate_short_url(existing_urls):
chars = string.ascii_letters + string.digits
while True:
code = ''.join(random.choices(chars, k=6))
if code not in existing_urls:
return code
I've seen this kill the performance of early-stage startups. You don't want your database to be the bottleneck just because you're guessing random strings and hoping for the best.
The Elegance of Base-62 Encoding
The professional way to handle this is to stop guessing and start calculating. Instead of treating the "short code" as a random string, treat it as a base-62 representation of a database ID.
Think about it: your database already gives you a unique, incrementing integer as a primary key (1, 2, 3...). If you convert that integer from base-10 to base-62, you get a unique, short string every single time. No collisions, no loops, and no unnecessary database queries to check if a code exists.
def encode(num):
# Our alphabet: 0-9, a-z, A-Z
alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
if num == 0:
return alphabet[0]
arr = []
base = len(alphabet)
while num:
num, rem = divmod(num, base)
arr.append(alphabet[rem])
arr.reverse()
return ''.join(arr)
# Example: Database ID 100,000 becomes 'g6s'
print(encode(100000))
This is significantly more efficient. You're essentially just changing the "language" the number is written in. It's deterministic, meaning ID 100,000 will always be 'g6s', and you'll never have two different IDs mapping to the same short code.
Mapping IDs to Short Codes in the Backend
Now, the actual flow of your backend changes. Instead of generating a code and then saving the URL, you save the URL first, get the auto-incremented ID back from the database, and then encode that ID to create your short link. When a user visits your.sh/g6s, you simply reverse the process: decode 'g6s' back into the integer 100,000, and fetch the record with that ID.
I'll admit, it feels slightly less "magical" than random strings, but in production, predictability is your best friend. You can calculate exactly how many URLs you can support before you need to add another character to your short code. For instance, a 7-character base-62 string can handle over 3.5 trillion URLs. That's plenty for most of us.
📋 Practical Task
Build a Bidirectional Base-62 URL Encoder/Decoder
Your task is to implement the counterpart to the encode function. You need to create a decode function that takes a base-62 string (the short code) and converts it back into its original integer database ID.
Requirements:
- Use the same alphabet:
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". - The
decodefunction should take a string and return an integer. - Test your implementation to ensure that
decode(encode(123456))returns123456.
def encode(num):
alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
if num == 0: return alphabet[0]
arr = []
base = len(alphabet)
while num:
num, rem = divmod(num, base)
arr.append(alphabet[rem])
arr.reverse()
return ''.join(arr)
def decode(code):
# TODO: Implement this function
pass
# Test your code here
test_id = 987654321
encoded = encode(test_id)
decoded = decode(encoded)
print(f"Original: {test_id} -> Encoded: {encoded} -> Decoded: {decoded}")
print(f"Success: {test_id == decoded}")
There are no comments for now.