Have you ever wondered how platforms like LeetCode work behind the scenes?
How do they handle millions of code submissions, execute them safely, and manage live programming contests? In this comprehensive tutorial, we'll walk through designing a complete LeetCode-like system from scratch.
What you'll learn:
How to gather and analyze system requirements
API and design fundamentals
Secure code execution strategies
Real-time leaderboard implementation
Advanced patterns
Don't worry if you're new to system design – we'll explain everything step by step!
What is LeetCode?
LeetCode is an online platform where programmers can:
Browse coding problems
Submit solutions in various programming languages
Get instant feedback on their code
Participate in timed programming contests
View live leaderboards during competitions
Now, let's design our own version!
Step 1: Gathering Requirements
Before writing any code or drawing diagrams, we need to understand exactly what our system should do. This is called requirements gathering, and it's the foundation of good system design.
Functional Requirements (What the system should do)
View Problems: Users can browse a list of coding problems and view individual problem details
Submit Code: Users can submit their solutions and get results (pass/fail)
Contest Support: Host programming contests with time limits
Live Leaderboard: Show real-time rankings during contests
Non-Functional Requirements (How well the system should perform)
Scale: Support 50,000 concurrent users
Latency: Leaderboard updates within 10 seconds
Security: Safely execute potentially malicious user code
Performance: Fast code execution and submission processing
💡 Pro Tip: Always start with requirements! They guide every decision you'll make in your design.
Step 2: API Design
The API defines how clients communicate with our system. Think of it as the "menu" of actions users can perform.
Core Endpoints
# Get a single problem
GET /problems/{problemId}
# Get list of problems (with pagination)
GET /problems?offset=0&limit=50
# Submit code for evaluation
POST /submit/{problemId}
A message queue acts like a buffer between your web server and code execution workers. Benefits include:
Reliability: If a worker crashes, the job stays in the queue
: Add more workers during high traffic
Decoupling: Web server doesn't wait for slow code execution
Popular message queues: Apache Kafka, RabbitMQ, Amazon SQS
Step 5: Secure Code Execution Deep Dive
This is one of the most critical parts of our system. Let's explore different approaches to safely execute untrusted code.
Option 1: Virtual Machines (VMs)
Pros: Maximum isolation, very secure
Cons: Slow to start, expensive
Option 2: Containers (Recommended)
Pros: Fast startup, good isolation, resource limits
Cons: Slightly less secure than VMs (but sufficient)
Option 3: Cloud Functions
Pros: Auto-scaling, managed infrastructure
Cons: Vendor lock-in, latency issues
Option 4: WebAssembly (Creative)
Pros: Runs in user's browser, no server costs
Cons: Limited language support
Our Choice: Docker Containers
Docker gives us the best mix of isolation, speed, and cost.
Container Configuration:
No network access
Limited file system
Memory limit (e.g., 128MB)
CPU and timeout limits
Execution Flow
Receive code submission
Create a secure Docker container
Execute code against test cases
Compare output with expected results
Return pass/fail
Destroy container
Step 6: Real-Time Leaderboards
How do we handle thousands of users watching leaderboards?
Naive Approach (Don’t do this!)
SELECT user_id, SUM(points) as total_score
FROM submissions
WHERE contest_id ='current_contest'AND status ='accepted'GROUPBY user_id
ORDERBY total_score DESC;
Problems:
Slow, especially with lots of data
Poor performance under load
Smart Approach: Sorted Sets
Redis offers in-memory Sorted Sets that auto-maintain order.
Benefits:
O(log n) insert
O(n) retrieval
High-speed leaderboard updates
Leaderboard Example
// Add user score
redis.zadd('contest_123_leaderboard', user_score, user_id);
// Get top 10
redis.zrevrange('contest_123_leaderboard', 0, 9, 'WITHSCORES');
Updating in Real Time
Client-side polling every 10 seconds works well:
50,000 users → 5,000 requests/second
Redis handles this easily
Bonus: Use for true real-time but requires more infra