Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Coding Duel Platform

Real-time 1v1 competitive coding — LeetCode meets Chess Elo.

Two players. One problem. A live code editor, hidden test cases, and an Elo rating system that updates the moment the match ends. Built end-to-end as a solo project.


Table of Contents


Overview

Coding Duel is a competitive programming platform where players are matched in real-time based on their Elo ratings, assigned a coding problem suited to their skill level, and race to solve it in a shared browser-based IDE. The platform supports multiple win conditions — first to pass all test cases, most test cases solved before time expires, or opponent surrender — mirroring the match dynamics of competitive chess.


Modules

The platform is built around 12 modules. Each module handles one clear part of the system and works together with the others to deliver the full experience.


Module 1 — User Registration & Authentication

This is the entry point of the platform. Before a user can play any match, they need to create an account and prove who they are.

When a new user signs up, they provide their name, email, and password. The system sends a 6-digit verification code to their email — they must enter this code before their account becomes active. This step ensures that only real, valid email addresses are accepted.

Once verified, the user can log in. The system issues a secure login token (called a JWT — think of it as a digital key) that the user carries with every request they make. The server checks this key to confirm the user is who they claim to be without asking them to log in again on every click.

Key features:

  • Account creation with email and password
  • Email verification using a one-time code (OTP) that expires after a set time
  • Secure login that issues a token, and logout that permanently cancels that token
  • Forgot password flow — user requests a reset link, receives a new OTP, and sets a new password
  • Protection against banned users — if an admin bans an account, the user cannot log in again

Module 2 — Real-Time Matchmaking

This module is responsible for finding two players who are close in skill level and pairing them into a match — automatically and instantly.

When a player clicks "Find Match," the system looks at their current rating (Elo score) and places them into a waiting group — called a bucket — with other players of a similar rating. Think of it like a queue at a ticket counter, but there are separate counters for beginners, intermediate, and advanced players.

The moment two players end up in the same bucket, the system pairs them, creates a match, and notifies both of them at the same time. If no opponent is found immediately, the player waits in the lobby and can see how many other players are currently searching.

Key features:

  • Players are sorted into rating buckets (e.g. 0–99, 100–199, 200+ and so on) so matches are always fair
  • The waiting queue is powered by Redis — an ultra-fast in-memory system — so pairing happens in milliseconds
  • Players see a live lobby showing estimated wait time and number of active players searching
  • Players can cancel matchmaking at any time and leave the queue cleanly

Module 3 — Elo Rating System

The Elo rating system is a well-established method for measuring player skill. It was originally invented for chess and is now used in games like League of Legends, Chess.com, and competitive programming platforms.

Every new player starts with a base rating of 50. After each match, their rating goes up or down depending on whether they won or lost, and also how strong their opponent was. Beating a stronger opponent earns more points than beating a weaker one.

Key features:

  • Ratings update automatically at the end of every match — win, lose, draw, surrender, or time expiry each have their own rules
  • A player who surrenders loses rating just like a normal loss — same as resigning in chess
  • If a match is mutually aborted (both players agree to cancel), no ratings change
  • If the time limit runs out, the player who solved more test cases wins; if it's a tie, it counts as a draw and ratings stay the same
  • The system uses the standard Elo formula with a K-factor (a value that controls how much ratings shift per match)

Module 4 — Problem Assignment Engine

Once two players are matched, the system needs to give them a coding problem to solve. This module handles selecting the right problem for each match — fairly and without repeats.

Problems in the database are tagged by difficulty level — easy, medium, or hard — and these difficulty levels are aligned with the Elo buckets. Players with lower ratings receive easier problems, and higher-rated players receive harder ones. This ensures the challenge is always appropriate for the skill level of the players involved.

Key features:

  • Problems are filtered by difficulty based on the players' rating range — beginners do not get hard problems, and advanced players do not get easy ones
  • The system checks both players' match history from the past 24 hours and avoids assigning a problem either player has already seen recently — this prevents repetitive matches
  • If every suitable problem has been seen recently, the system widens its search until it finds a fresh one
  • Both players always receive the exact same problem — the competition is completely symmetric

Module 5 — In-Browser Code Editor

This module gives players a professional coding environment directly inside the browser — no installation required. Players can write, edit, and submit code without ever leaving the platform.

The editor is built using Monaco Editor, which is the same editor that powers Microsoft Visual Studio Code. Players will find it familiar and comfortable to use.

Key features:

  • Supports multiple programming languages — players can choose their preferred language before starting
  • Syntax highlighting — keywords, functions, and variables are color-coded to make code easier to read and write
  • The layout mirrors LeetCode — the problem statement is on one side, the code editor on the other, so players can read and code at the same time
  • The editor includes a submit button that sends the code to the backend for evaluation

Module 6 — Test Case Validation

When a player submits their code, this module takes over. It sends the code to an external execution engine called Judge0, which runs the code against a series of test cases and reports back whether the solution is correct.

Test cases are pre-written inputs and expected outputs stored for each problem. The system feeds the player's code each input one by one, checks what the code outputs, and compares it to the expected answer.

Key features:

  • Code is executed in a completely isolated and sandboxed environment — it cannot affect the server or other players
  • Supports all major languages — the same Judge0 engine handles C++, Python, Java, JavaScript, and more
  • Results are categorized clearly:
    • AC (Accepted) — the code output matched the expected output
    • WA (Wrong Answer) — the code ran but gave the wrong result
    • RTE (Runtime Error) — the code crashed while running
    • CTE (Compile Error) — the code could not even be compiled or interpreted
    • TLE (Time Limit Exceeded) — the code took too long to run
  • The actual test case inputs and expected outputs are never shown to the player — only the result category and pass/fail count are displayed, exactly like LeetCode

Module 7 — Live Game Updates

This module keeps both players connected to each other and to the match in real time. Without this module, players would have no idea what their opponent is doing unless they refreshed the page.

It uses a technology called WebSockets, which is a permanent two-way connection between each player's browser and the server. Unlike a normal web request where the browser asks and the server answers, WebSockets allow the server to push information to the browser the moment something happens — no asking required.

Key features:

  • The moment a match is found, both players receive a notification at exactly the same time and are directed to the duel room
  • When either player submits code and results come back from Judge0, both players see the updated test case status instantly
  • The winner is announced live — the moment one player passes all test cases, both players see the result card without any delay
  • Each match has its own private room identified by a unique room ID — only the two players in that match are connected to that room, so there is no interference between different matches

Module 8 — Duel History & Replay Viewer

Every match that takes place on the platform is saved permanently. This module gives players access to their full history of past matches so they can review their performance, see where they went wrong, and learn from previous duels.

Key features:

  • Every completed match is stored in the database with full details — who played, what problem was given, what each player submitted, who won, and what the final rating changes were
  • Players can visit their match history page and scroll through past matches sorted by date
  • For each match, players can see the final code both they and their opponent submitted — this is the "replay" aspect, letting players compare approaches
  • Win, loss, and draw records are tracked over time, giving players a clear picture of how their skill has progressed

Module 9 — Leaderboard Management

The leaderboard is a ranked list of all players on the platform, sorted by their Elo rating. It gives players a sense of where they stand globally and motivates them to keep improving.

Key features:

  • A global leaderboard shows all registered players ranked from highest to lowest Elo rating
  • Each player can see their exact rank number (e.g. #245 globally) alongside their rating, number of matches played, and win rate
  • The leaderboard updates in real time as matches are completed and ratings change
  • A seasonal leaderboard resets periodically, giving all players a fresh start and creating a competitive cycle — similar to ranked seasons in games like Valorant or Chess.com

Module 10 — Admin Dashboard

The admin dashboard is a private, restricted area of the platform that only administrators can access. It gives the admin team full visibility and control over everything happening on the platform.

Admins can manage every part of the system from one central place without needing to touch the database directly.

Key features:

  • User management — admins can view all registered users, see their activity, and ban accounts that violate platform rules
  • Problem management — admins can add new coding problems, update existing ones, delete outdated ones, and add or edit test cases for each problem
  • Match oversight — admins can view all matches that have taken place, including who played, what the outcome was, and when it happened
  • Platform statistics — a stats page gives admins a high-level overview of total users, total matches played, active sessions, and other key numbers
  • Feedback management — all user-submitted feedback appears in the admin panel so the team can review and resolve issues (covered in detail in Module 11)

Module 11 — Feedback & Support System

This module gives users a way to report problems, request features, or ask for help — and gives the admin team the tools to respond and resolve those requests in an organized way.

Rather than users sending emails to a personal inbox or posting on social media, everything goes through a structured form inside the platform. This keeps feedback organized and trackable.

Key features:

  • Feedback submission form — users can submit feedback by choosing an issue type (bug, feature request, general inquiry), writing a title and description, and selecting a severity level (low, medium, or high). They can also choose to submit anonymously if they prefer not to share their identity, and optionally attach a screenshot of the issue
  • Admin feedback queue — all submitted feedback appears in the admin panel in a list that can be filtered by severity, issue type, or whether it has been resolved yet. This makes it easy for admins to prioritize urgent issues
  • Resolution tracking — when an admin resolves a piece of feedback, they mark it as resolved. The admin stats dashboard shows a live count of how many feedback items are open vs. resolved, giving a clear picture of the support workload at any time

Module 12 — Notification & Activity System

This module keeps users informed about what is happening on the platform, both in real time and as a historical record they can look back at.

It has two distinct parts that work differently but serve the same goal — making sure users never miss anything important.

Part A — Real-Time Notifications (the bell icon)

When something significant happens — a match is found, a match result is decided, an Elo change occurs, or feedback is resolved — the user sees an instant notification popup in the top bar of the interface, no matter which page they are currently on. This is powered by the same WebSocket connection used in the duel room, but scoped to the individual user rather than a match room.

Think of it like a phone notification — it appears immediately without the user having to refresh or navigate anywhere.

Part B — Activity Feed (the dashboard card)

Separately from the live notifications, the user's dashboard shows a "Recent Activity" card that lists their last 5–10 actions in chronological order. Examples: "Won a match vs PlayerX (+14 Elo) — 2 hours ago", "Submitted feedback — 1 day ago", "Solved Problem #42 (Easy) — 1 day ago."

Unlike the bell notifications which disappear once dismissed, the activity feed is a permanent log that is always available on the dashboard whenever the user comes back to it.

Key features:

  • Real-time bell icon with unread badge count that increments automatically when new events arrive
  • Notification dropdown listing recent events with icons and relative timestamps (e.g. "2 min ago")
  • "Mark all as read" button to clear the badge count
  • Email notifications for important account events — account verified, password reset completed, account banned — sent via the platform's existing email system
  • Persistent activity feed on the dashboard built from the match history and feedback tables already stored in the database — no extra storage needed

System Architecture

┌──────────────────────────────────────────────────────────────────┐
│  CLIENT LAYER                                                    │
│  Next.js 14 (App Router)  ·  Monaco Editor  ·  Zustand          │
└──────────┬───────────────────────────────┬───────────────────────┘
           │ REST / HTTPS                  │ WebSocket
┌──────────▼───────────────────────────────▼───────────────────────┐
│  API GATEWAY & AUTH                                              │
│  Express Router  ·  JWT Middleware  ·  WS Server  ·  Rate Limit  │
└──────┬───────────┬────────────────────────┬──────────────────────┘
       │           │                        │
┌──────▼──┐  ┌─────▼──────┐  ┌─────────────▼──────┐  ┌───────────┐
│Matchmak-│  │  Problem   │  │  Elo Calculator    │  │   Code    │
│ing Eng. │  │  Engine    │  │  Win/Loss/Draw     │  │Submission │
│Elo buck-│  │Elo-filter  │  │  K-factor scaling  │  │Judge0 Bri-│
│ets·FIFO │  │24h dedup   │  │  Surrender logic   │  │dge·Verdict│
└──────┬──┘  └─────┬──────┘  └──────────┬─────────┘  └─────┬─────┘
       │           │                    │                   │
┌──────▼───────────▼────────────────────▼───────────────────▼─────┐
│  DATA & INFRASTRUCTURE                                          │
│  Redis 7 (queues · match cache)                                 │
│  PostgreSQL 16 via Drizzle ORM (users · matches · problems)     │
│  Docker → Judge0 CE (sandboxed execution) → k8s (prod)          │
└──────────────────────────────────────────────────────────────────┘

Request lifecycle — matchmaking

Player clicks "Find Match"
  → POST /queue-for-match
  → Auth middleware validates JWT
  → Matchmaking Engine reads player Elo
  → Computes bucket key (e.g. "bucket:0-99")
  → RPUSH player data onto Redis queue
  → Poll: LLEN >= 2? → LPOP two entries
  → Problem Engine selects a problem (Elo-filtered, deduped)
  → Match object created (roomID, player1, player2, problem, testCases)
  → Stored in PostgreSQL + Redis (TTL cache)
  → WS Server emits `match:found` to both players on roomID channel
  → Both clients redirect to /duel/[roomID]

Request lifecycle — code submission

Player submits code
  → POST /all-test-check  { code, language, problemId }
  → Code Submission service fetches all testCases for problemId
  → Batches { source_code, language_id, stdin } → Judge0 REST API
  → Judge0 runs each case in a sandboxed container
  → Returns per-case: stdout, stderr, status, time, memory
  → Verdict analysis: RTE / CTE / WA / AC, pass count
  → WS Server emits `submission:result` on roomID
  → Both players receive updated test case status
  → If AC (all passed): emit `match:win` / `match:lose`
  → Elo Calculator updates both ratings in PostgreSQL

Tech Stack

Layer Technology
Frontend Next.js 14, TypeScript, Zustand, Tailwind CSS, Monaco Editor
Backend Node.js, Express.js, TypeScript, ws (WebSockets)
Database PostgreSQL 16, Drizzle ORM
Cache / Queue Redis 7 (FIFO queues per Elo bucket, match object TTL cache)
Code Execution Judge0 Community Edition, Docker (→ Kubernetes in production)
Auth JWT, email OTP verification, session revocation table

Database Schema

users

Column Type Notes
id uuid PK defaultRandom()
username text unique
email text unique
password text hashed
rating integer default 50 (starting Elo)
verified boolean email verified flag
banned boolean admin-controlled
oauthId text nullable, for OAuth providers
codeExpirey timestamp OTP expiry

match

Column Type Notes
id serial PK
roomId uuid unique per match
problemId integer FK → problems
player1 jsonb player snapshot
player2 jsonb player snapshot
winner text nullable until resolved
rated boolean whether Elo changes apply
totalCases integer total test cases in problem
solution jsonb both players' final code
status text ongoing | completed | draw

problems

Column Type Notes
id serial PK
problemId integer unique display ID
title text
statement text problem description
input / output text format description
constraints text
testCases jsonb array of { input, output }

user_sessions

Tracks active JWT sessions with revocation support (revoked, expiresAt).

marathon_match

Stores marathon session data: problems (JSONB array of solved problems), totalTime, status.

feedbacks

User-submitted feedback with issueType, severity, anonymous flag, and resolved status.

notifications (new — Module 12)

Column Type Notes
id serial PK
userId uuid FK → users
type text match_found | match_won | match_lost | feedback_resolved | account_event
title text display text
read boolean default false
createdAt timestamp defaultNow()

Matchmaking Algorithm

Players are assigned to Redis queues based on Elo bucket ranges:

Bucket Elo Range Problem Difficulty
bucket:0 0 – 99 Easy
bucket:1 100 – 199 Easy–Medium
bucket:2 200 – 349 Medium
bucket:3 350 – 499 Medium–Hard
bucket:4 500+ Hard
queueForMatch(player):
  bucketKey = getBucket(player.rating)
  RPUSH bucketKey serialize(player)

  if LLEN(bucketKey) >= 2:
    p1 = LPOP(bucketKey)
    p2 = LPOP(bucketKey)
    problem = selectProblem(bucketKey, [p1.history, p2.history])
    createMatch(p1, p2, problem)
  else:
    wait (show lobby with estimated wait + active players)

Problem deduplication: If either player solved the candidate problem in the last 24 hours, the engine re-samples from the pool until a fresh problem is found.


Code Execution Pipeline

Client  →  POST /all-test-check
           { code: string, language: string, problemId: number }

Backend:
  1. Fetch all testCases WHERE problemId = ?
  2. For each { input, expectedOutput }:
       POST judge0/submissions { source_code, language_id, stdin }
  3. Poll for results (or use callbacks)
  4. Analyze verdict array:
       - All AC → match win
       - Any RTE / CTE → surface error type (not raw stderr)
       - Count passed / total → emit partial progress

WebSocket:
  emit('submission:result', { passed, total, verdict, time, memory })
  if allPassed: emit('match:win') to winner, emit('match:lose') to opponent

Test case inputs/outputs are never sent to the client — only pass/fail counts and error categories are exposed, matching competitive platform standards.


Elo Rating System

Rating changes use the standard Elo formula with a fixed K-factor:

expected = 1 / (1 + 10^((opponentRating - playerRating) / 400))
newRating = currentRating + K * (actual - expected)

Where:
  actual = 1.0  (win)
         = 0.5  (draw / time-expiry tie)
         = 0.0  (loss / surrender)
  K      = 32   (adjustable per tier)

Special cases:

  • Abort (mutual): no Elo change for either player
  • Time-expiry: player with more test cases passed wins; tie → draw
  • Surrender: treated as a loss for the surrendering player

API Reference

Auth routes

Method Endpoint Description
POST /signin Sign in, returns JWT
POST /create-account Register new user
POST /forget-password Send OTP to email
POST /reset-password Reset with OTP
POST /verify-account Email verification
POST /logout-account Revoke session

Match routes

Method Endpoint Description
POST /get-stats Player stats
POST /queue-for-match Enter matchmaking
POST /cancel-matchmaking Dequeue player
POST /time-expiry Trigger time-expiry resolution
POST /notify-passed-all Signal all test cases passed
POST /notify-loss Signal match loss
POST /match-abort Abort current match
POST /match-rankings Leaderboard data
POST /match-history Past matches for player

Code routes

Method Endpoint Description
POST /all-test-check Submit code, run all test cases
POST /problem Fetch problem by ID

Admin routes

Method Endpoint Description
GET /admin-stats Platform-wide statistics
GET /admin-users All users
GET /admin-feedbacks All feedback
GET /admin-matches All matches
GET /admin-problems All problems
POST /admin-user-activity User activity log
POST /admin-feedback-activity Feedback activity
POST /admin-add-problem Add problem
POST /admin-delete-problem Delete problem
POST /admin-update_problem Update problem
POST /admin-add-testCase Add test case to problem

Feedback routes

Method Endpoint Description
POST /add-feedback Submit user feedback
GET /admin-feedbacks List all feedback (admin)
POST /admin-feedback-activity Feedback activity log (admin)

Notification & activity routes (new — Module 12)

Method Endpoint Description
GET /notifications Paginated notifications for current user
POST /notifications/mark-read Mark all notifications as read
GET /activity Recent activity feed for dashboard

Getting Started

Prerequisites

  • Node.js 20+
  • PostgreSQL 16
  • Redis 7
  • Docker (for Judge0)

1. Clone the repository

git clone https://github.com/your-username/coding-duel-platform.git
cd coding-duel-platform

2. Start Judge0 via Docker

cd judge0
docker compose up -d

Judge0 will be available at http://localhost:2358 by default.

3. Install dependencies

# Backend
cd server
npm install

# Frontend
cd ../client
npm install

4. Configure environment variables

See Environment Variables below.

5. Run database migrations

cd server
npx drizzle-kit push

6. Start the development servers

# Backend (from /server)
npm run dev

# Frontend (from /client)
npm run dev

Environment Variables

Backend (/server/.env)

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/coding_duel

# Redis
REDIS_URL=redis://localhost:6379

# Auth
JWT_SECRET=your_jwt_secret_here
JWT_EXPIRY=7d

# Judge0
JUDGE0_BASE_URL=http://localhost:2358
JUDGE0_AUTH_TOKEN=           # leave empty for local CE

# Email (SMTP)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your@email.com
SMTP_PASS=your_smtp_password

# Server
PORT=4000
NODE_ENV=development

Frontend (/client/.env.local)

NEXT_PUBLIC_API_URL=http://localhost:4000
NEXT_PUBLIC_WS_URL=ws://localhost:4000

Project Structure

coding-duel-platform/
├── client/                   # Next.js frontend
│   ├── app/                  # App Router pages
│   │   ├── (auth)/           # Login, register, verify
│   │   ├── dashboard/        # Main player dashboard
│   │   ├── duel/[roomId]/    # Live duel room
│   │   ├── leaderboard/      # Rankings
│   │   ├── marathon/         # Marathon mode
│   │   └── admin/            # Admin panel
│   ├── components/           # Shared UI components
│   ├── store/                # Zustand state slices
│   └── lib/                  # API client, WS helpers
│
├── server/                   # Express backend
│   ├── controllers/
│   │   ├── authController.ts
│   │   ├── matchController.ts
│   │   ├── codeCheck_Controller.ts
│   │   ├── generalController.ts
│   │   └── AdminController.ts
│   ├── routes/               # Express router
│   ├── db/
│   │   ├── schema.ts         # Drizzle schema definitions
│   │   └── index.ts          # DB connection
│   ├── services/
│   │   ├── matchmaking.ts    # Redis queue logic
│   │   ├── elo.ts            # Rating calculation
│   │   ├── judge0.ts         # Judge0 API client
│   │   └── websocket.ts      # WS room management
│   └── middleware/
│       ├── auth.ts           # JWT validation
│       └── rateLimiter.ts
│
└── judge0/                   # Docker Compose for Judge0
    └── docker-compose.yml

Roadmap

  • Spectator mode — watch live matches in real time
  • Custom room creation — private duels with invite links
  • Team mode — 2v2 collaborative solving
  • Problem submission portal — community-contributed problems with admin review
  • Kubernetes deployment manifests for Judge0 horizontal scaling
  • OAuth (GitHub / Google) login
  • Mobile-responsive duel room layout

Author

Salman Ahmed Khan
Software Engineer · SZABIST Islamabad, Class of 2026
LinkedIn · GitHub


Built with Node.js · Next.js · Redis · PostgreSQL · Judge0 · Docker

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages