My Experience Building a Leetcode-Like Online Judge and How You Can Build One

My Experience Building a Leetcode-Like Online Judge and How You Can Build One

Built Your Own Online Judge from Scratch

Introduction

We all love bikes, don’t we? Imagine the satisfaction of building your own custom bike! I know, I know, It might not be the best in town, but the pride of turning your passion into a product is unmatched.

As software engineers, we love creating software, often relying on various tools to support our development process. Now, imagine building one of those tools. It might not be production-grade, but the satisfaction of mimicking the functionalities of established software is immense. I have one such story to share with you!

One day, while leetcoding, out of nowhere, seeing the green AC text, I found myself wondering how platforms like leetcode and other online judge platforms execute and evaluate submitted code and generate a result. This curiosity led me to develop my own backend solution — an online judge just like leetcode!

In this post, I’ll walk you through how I created a backend solution — https://github.com/Mahboob-A/Algocode (though not as refined as leetcode, of course, but still a work of pride! :P) as an online judge just like leetcode. I’ll also guide you on how you can build one yourself. Let’s get started!

Architecture of the Project

The Algocode I have built is based on microservices architecture. Why microservices? Well, some systems just don’t fit well within a monolithic structure, and an online judge backend is a prime example. A monolithic approach won’t be able to provide the modularity scalability, flexibility and most importantly the security a Remote Code Execution Engine needs to actually execute foreign source code into your server. A microservices approach is natively modular, scalable, flexible and we can customize the security we need for any services.

Architecture of Algocode — A Leetcode Like Online Judge in Microservices

Algocode currently consists of three services: Auth Service, Code Manager, and an RCE Engine for C++ Judge. As of now, Algocode fully supports C++ code execution, and RCE Engine for Java is under development. Future updates will include support for Python. I'll keep this post updated as new features are implemented and RCE engine for Python and Java are ready.

Now, it’s time for some high level understanding into the details of the services that make up the Algocode backend.

Services

The backend for all the services is built using Django 4.2. Here, I’ll give you an overview of the functionalities of Algocode services. For detailed instructions, I strongly recommend to visit Algocode on GitHub for more detailed and step by step guide.

Algocode Auth Service

The Algocode Auth Service serves as the main entry point to Algocode. It manages user registration, authentication, and authorization, using a PostgreSQL database for user data management. For more details, please check out the Algocode Auth Service.

Algocode Code Manager Service

The Code Manager Service is the intermediary between clients submitting their code and the RCE Engine, which executes it. It manages communication between clients and the RCE Engine, which isn’t directly accessible to clients. This communication happens through a message queue (RabbitMQ).

The Code Manager exposes all necessary APIs for clients to submit solutions, check problem lists, submit code, and check results. When a client sends a code submission request, the Code Manager validates it, retrieves test cases and correct answers from a PostgreSQL database, and publishes the processed data to a language-specific queue. The RCE Engine then consumes this data, executes the code, and publishes the result to a unified-result queue.

The Code Manager then processes, caches, and stores the result in a MongoDB database. For more information, visit the Code Manager service. It’s well documented and you’ll love it, I guarantee!

Algocode RCE Engine Service

The Algocode RCE Engine Service is the heart of Algocode. It executes user-submitted code in a secure Docker environment and compares the output against test case answers. RCE Engine can handle any malicious code execution such as fork bomb, resource starvation and file hijacking to name a few. The final result is processed and published to a unified-result queue, which the Code Manager service then consumes to store the result.

The RCE Engine for C++ Judge can handle the following events:

a. AC (Accepted) 
b. WA (Wrong Answer) 
c. Compilation Error 
d. Time Limit Exceeded 
e. Memory Limit Exceeded 
f. Segmentation Fault

How to Build Your Own Online Judge

If you’re still reading this section, congratulations! You’re one of the most curious individuals I have ever met. Since you’re still here, it’s clear you’re eager to learn how to build your own online judge. We’re software engineers, after all!

Building your own judge is a challenging but rewarding process. If you’re new to microservices architecture, it can be intimidating to manage the various SDLC cycles of all the services. However, this experience will provide you with a deep understanding of maintaining a microservices project through various development stages.

Before you dive into building an online judge, I recommend familiarizing yourself with the following concepts. Assuming you have already built a monolith-based CRUD app with API integration, these foundational concepts will prepare you for the complexities ahead. You don’t need to master them, just have an initial understanding. You’ll gain deeper insights as you build your project.

A. Microservices Architecture

Microservices architecture offers numerous benefits. It’s crucial for you to understand the benefits of scalability, maintainability, agility, and security that microservices architecture provides.

B. Asynchronous Communication

A key aspect of microservices architecture is asynchronous communication. Since microservices often involve multiple services that might be hosted globally, asynchronous communication is essential. If you’re wondering why not synchronous, you need to revisit the core benefits of microservices. Learn about RabbitMQ for effective asynchronous communication between microservices.

C. Docker

Docker is a fundamental component of this project. In modern tech, building software without Docker is almost unthinkable. Your service will be dockerized, and the online judge will use Docker containers to execute user-submitted code securely. You should understand Docker networking, Docker volumes, Docker security, and Docker Compose to tackle a complex project like an online judge.

D. Database

A solid understanding of both SQL and NoSQL databases like MySQL, PostgreSQL, and MongoDB is crucial. In the software industry, there’s no “always best” solution, only “best fit.” As we handle both structured and unstructured data, choosing between SQL and NoSQL databases depends on the use case. Algocode utilizes PostgreSQL and MongoDB.

E. Extra Concepts

Beyond the basics, incorporating several additional concepts can scale your project. Knowledge of file handling, caching, rate limiting, polling, API testing, version control, and development stage management will make your project more eye-catching and industry-ready.

Learning Outcome

Building an online judge project using microservices architecture is challenging. Because you’ll be running code submitted by unknown users on your server, the security aspects of the project are critical. As you progress, you’ll gain a deep understanding of how to secure your codebase. You will learn about spawning sibling containers, managing these containers, implementing the docker in docker concept, handling complex file operations, caching, rate limiting APIs, and ensuring seamless asynchronous communication between services. Additionally, you’ll discover many unsuccessful methods to do a certain thing and gain valuable insights by troubleshooting various bugs and errors along the way.

Resources

Did you find this article valuable?

Support The Backend Diaries by becoming a sponsor. Any amount is appreciated!