System Design: LeetCode
I would design a scalable online coding platform that allows users to browse programming problems, write code in multiple languages, execute sample tests, submit solutions against hidden test cases, and track their progress. I would clarify the scope first and treat contests, leaderboards, discussions, and recommendations as extensions unless explicitly required. The most challenging subsystem is the secure execution and evaluation of untrusted code at scale.
I would begin with illustrative assumptions of 10 million registered users, 1 million daily active users, and five submissions per active user per day. That gives approximately 5 million submissions daily, or 58 submissions per second on average. I would design for a peak of around 600 submissions per second, then refine the estimate with actual traffic patterns, average execution duration, language distribution, and contest behavior. Browsing is read-heavy and cacheable, whereas judging is CPU-intensive and bursty, so these workloads must scale independently.
The main functional requirements are problem discovery and filtering, code editing, execution against sample or custom input, submission against hidden test cases, verdict reporting, submission history, and progress tracking. The non-functional requirements include secure sandboxing, high availability, low perceived latency, durable submission records, fair resource allocation, and horizontal scalability.
I would expose APIs such as GET /problems, GET /problems/{id}, POST /runs, POST /submissions, GET /submissions/{id}, and GET /users/{id}/submissions. The run endpoint would evaluate selected sample or custom tests, while the submission endpoint would trigger full evaluation. Submission creation would return HTTP 202 Accepted with a submission ID and queued status rather than keeping the request open while code executes. The client could retrieve status through polling or receive updates through Server-Sent Events or WebSockets.
The core data model would include User, Problem, Submission, TestBundle, Contest, and ContestParticipant. PostgreSQL would store users, problem metadata, submission status, verdicts, and contest records. Submission records would include the user ID, problem ID, language, source-code reference, status, verdict, execution time, memory usage, and timestamps. Large test bundles and optionally source-code artifacts could be stored in object storage, with versioned references in the database. I would add indexes for user submission history and problem-level queries before considering partitioning or sharding.
The architecture would have a CDN and API gateway in front of separate problem and submission services. The problem service would read from PostgreSQL and use caching for frequently accessed problem statements and metadata. The submission service would validate requests, enforce rate limits, persist submission records, and publish durable judging jobs. A message queue would absorb bursts and decouple API traffic from worker capacity. Judge workers would consume jobs, fetch the appropriate language runtime and versioned test bundle, compile the source code where necessary, and execute tests in isolated sandboxes. A result service would persist verdicts and notify clients.
For execution, I would evaluate hardened containers with gVisor and microVMs such as Firecracker. Ordinary containers are efficient but share the host kernel, so they should not be treated as a complete security boundary for hostile code. MicroVMs provide a separate guest kernel and stronger isolation properties at some resource and startup cost. The decision would depend on measured latency, throughput, compatibility, and threat-model requirements. Every execution must enforce CPU, memory, wall-clock, process-count, output-size, filesystem, and network restrictions. User code must never run with privileged access to application services, credentials, or hidden test storage.
The submission workflow would be asynchronous. First, the API persists the submission with QUEUED status and ensures that the judging job will be published reliably. A transactional outbox or database-backed dispatcher can prevent the failure mode where the database commit succeeds but queue publication fails. Next, a worker claims the job and transitions it to RUNNING. It compiles and executes the solution against the appropriate test cases, stops when a terminal condition is reached, and calculates the verdict. The result service stores the final state and publishes a notification. Possible verdicts include ACCEPTED, WRONG_ANSWER, TIME_LIMIT_EXCEEDED, MEMORY_LIMIT_EXCEEDED, RUNTIME_ERROR, and COMPILE_ERROR.
For reliability, workers must tolerate duplicate job delivery. Result persistence and downstream side effects, such as contest scoring, should be idempotent. Infrastructure failures should be distinguished from user-code failures: a worker crash may justify a bounded retry, while a wrong answer should not. I would monitor queue age, job completion latency, worker utilization, sandbox failures, retry counts, and verdict distributions. Dead-letter handling and reconciliation jobs would help identify submissions that remain stuck.
To handle scale, I would cache popular problem metadata at the CDN and application layers, keep common language runtimes warm, cache immutable test bundles on workers, and autoscale the judge fleet using queue backlog and job age. During contests, separate queues or priority scheduling, per-user quotas, and bounded concurrency would prevent practice traffic or a single user from monopolizing compute. Redis sorted sets could serve frequently updated leaderboards, with durable contest results as the source of truth and reconciliation to correct discrepancies.
I would avoid prematurely introducing microservices for every feature, multiple databases, or a complex multi-region architecture. An initial implementation could use a modular application, PostgreSQL, a durable queue, object storage, Redis where useful, and an independently scalable judge-worker fleet. I would introduce partitioning, additional service boundaries, and multi-region deployment when measured load, availability targets, or team ownership justify them. The central architectural decision is to isolate expensive and untrusted code execution from the user-facing application while making submissions durable, repeatable, observable, and independently scalable.
Separate read-heavy problem browsing from CPU-intensive judging so each workload scales independently.
Use asynchronous submission processing with durable job delivery, bounded retries, and idempotent result handling.
Treat arbitrary user code as hostile and enforce a hardened sandbox with strict resource and network restrictions.
Keep problem metadata in a relational database and large immutable test bundles in object storage with versioned references.
Protect contest workloads using queue prioritization, fair scheduling, and independent capacity controls.
Prefer a simple architecture initially and introduce more complex scaling mechanisms in response to measured bottlenecks.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience