How Your Code Runs on Platforms Like LeetCode

Have you ever wondered what happens when you hit "Run" or "Submit" on coding platforms like LeetCode? It's an interesting process involving several backend steps to ensure your code is executed correctly and efficiently. Let's break it down in simple terms.

Code Submission

When you submit your code, it gets sent to the backend with the problem ID and other necessary details.

Backend Processing

The backend receives your code and quickly checks everything is in order.

Queueing System

Instead of running the code immediately, the backend places your submission into a queue. Think of it like a waiting line, ensuring each submission is handled in turn.

Worker System

Thousands of workers, like diligent staff members, pick up submissions from the queue and execute the code in a safe environment.

Result Handling

Once the code is executed, the results (output, execution time, errors) are sent back to the frontend, and you see the results.

System Design

Here's a high-level overview of the system architecture:

  1. Client: The user interface where developers write and submit their code.
  2. API Server: Handles incoming requests, authenticates users, and manages the submission process.
  3. Redis Queue: Stores submitted code in a queue for processing.
  4. Worker Nodes: Multiple instances that pull code from the queue and execute it in isolated environments.
  5. Result Storage: A database to store execution results.
  6. Load Balancer: Distributes incoming requests across multiple API servers for scalability.

The flow would be:

  1. User submits code through the client.
  2. API server receives the submission and pushes it to the Redis queue.
  3. Available worker nodes pull submissions from the queue.
  4. Workers execute the code in isolated environments.
  5. Results are stored in the database.
  6. API server fetches results and sends them back to the client.

This design allows for horizontal scaling of both API servers and worker nodes to handle high loads.

System Design for Code Execution Platform

Code Example

Here is a simple implementation of this whole logic using Express and Redis for the backend, and Postman for sending the data to the server. See how the data is picked by two workers one by one.

Code Link

import { createClient } from "redis";

const client = createClient();

async function processSubmission(submission: string) {
  const { problemId, code, language } = JSON.parse(submission);
  console.log(`Processing submission for problemId ${problemId}...`);
  console.log(`Code: ${code}`);
  console.log(`Language: ${language}`);

  // Here you would add your actual processing logic
  // Simulate processing delay
  await new Promise(resolve => setTimeout(resolve, 1000));

  console.log(`Finished processing submission for problemId ${problemId}.`);
}

async function startWorker() {
  try {
    await client.connect();
    console.log("Worker connected to Redis.");

    // Main loop
    while (true) {
      try {
        const submission = await client.brPop("problems", 0);
        await processSubmission(submission.element);
      } catch (error) {
        console.error("Error processing submission:", error);
      }
    }
  } catch (error) {
    console.error("Failed to connect to Redis", error);
  }
}

startWorker();
import express from "express";
import { createClient } from "redis";

const app = express();
app.use(express.json());

const client = createClient();
client.on('error', (err) => console.log('Redis Client Error', err));

app.post("/submit", async (req, res) => {
  const problemId = req.body.problemId;
  const code = req.body.code;
  const language = req.body.language;

  try {
    await client.lPush("problems", JSON.stringify({ code, language, problemId }));
    res.status(200).send("Submission received and stored.");
  } catch (error) {
    console.error("Redis error:", error);
    res.status(500).send("Failed to store submission.");
  }
});

async function startServer() {
  try {
    await client.connect();
    console.log("Connected to Redis");
    app.listen(3000, () => {
      console.log("Server is running on port 3000");
    });
  } catch (error) {
    console.error("Failed to connect to Redis", error);
  }
}

startServer();

Useful Links