DEV Community

Cover image for I Built a Real-Time 1v1 Coding Battle Platform in Days Using Kiro
Abdulkabir Musa
Abdulkabir Musa

Posted on

I Built a Real-Time 1v1 Coding Battle Platform in Days Using Kiro

The Problem with LeetCode

Let's be honest: grinding LeetCode is boring.

You solve a problem. Green checkmark. Move on. Repeat. There's no excitement, no pressure, no reason to push beyond "it works."

I wanted to change that. What if coding practice felt like a ranked match in your favorite game? What if you could watch your opponent's progress in real-time and race to submit first?

That's how Phantom was born—a real-time 1v1 competitive coding platform where developers battle head-to-head in algorithmic challenges.

What I Built

Phantom Battle Arena

Phantom features:

  • Live split-screen battles - Watch your opponent code in real-time
  • AI-powered judging - Google Gemini analyzes code quality and efficiency
  • Strategic power-ups - Time Freeze, Code Peek, Debug Shield
  • ELO matchmaking - Fair matches against similarly skilled opponents
  • Cyberpunk UI - Dark glassmorphism design with smooth animations

The tech stack:

  • Frontend: Next.js 14, React, TypeScript, TailwindCSS, Monaco Editor
  • Backend: Node.js, Express, Socket.io
  • Databases: PostgreSQL + Redis
  • Code Execution: Judge0 Cloud API
  • Deployment: Vercel + Railway

How Kiro Changed Everything

I built Phantom for the Kiroween Hackathon, and Kiro's spec-driven development approach was transformational.

Spec-Driven Development

Instead of diving straight into code, every major feature started with structured specs:

.kiro/specs/
├── phantom-code-battle/     # Core battle mechanics
├── power-ups-system/        # Strategic abilities
├── ai-code-coach/           # Gemini integration
├── judge0-cloud-integration/ # Code execution
└── authenticated-redirect/   # Auth flow
Enter fullscreen mode Exit fullscreen mode

Each spec contained:

  1. requirements.md - What the feature must do
  2. design.md - Architecture, data models, API contracts
  3. tasks.md - Implementation checklist

This structure forced me to think through edge cases before writing code. For a real-time multiplayer system where race conditions lurk everywhere, this was invaluable.

The Pivot That Could Have Killed the Project

Here's where Kiro really saved me.

I built a beautiful Docker-based code execution system locally. Sandboxed containers, resource limits, network isolation—it worked perfectly.

Then I tried deploying to Railway.

Railway doesn't support Docker-in-Docker.

My entire code execution engine was unusable in production. With the hackathon deadline approaching, I had to completely rewrite the execution pipeline to use Judge0's cloud API instead.

Because I had clear specs defining the interfaces and requirements, I could swap the execution layer without touching the battle logic. Kiro helped me plan the migration systematically:

// Before: Docker execution
async executeCode(code: string, language: string): Promise<ExecutionResult> {
  const container = await docker.createContainer({...});
  // ... container management
}

// After: Judge0 API
async executeCode(code: string, language: string): Promise<ExecutionResult> {
  const response = await judge0Client.submit({
    source_code: code,
    language_id: LANGUAGE_MAP[language],
  });
  // ... same interface, different implementation
}
Enter fullscreen mode Exit fullscreen mode

The spec-driven approach meant the rest of the codebase didn't care how code was executed—just that it returned the expected result format.

Debugging Railway Deployment

Deploying the backend to Railway was brutal:

  • Environment variables needed specific formatting
  • TypeScript errors only appeared in Railway's build environment
  • Health checks kept timing out
  • WebSocket connections dropped due to proxy issues
  • The dashboard kept timing out during deploys

When I hit walls, Kiro helped troubleshoot systematically. It analyzed error logs, suggested configuration fixes, and helped me understand Railway's specific requirements.

Eventually, I deployed everything via Railway's CLI after the dashboard proved unreliable:

railway login
railway link
railway up
Enter fullscreen mode Exit fullscreen mode

Sometimes the CLI just works better.

The Real-Time Challenge

Building multiplayer real-time features is genuinely hard. Here's what I learned:

WebSocket Architecture

// Socket.io event naming convention: action:resource
socket.on("battle:join", handleJoin);
socket.on("battle:submit", handleSubmit);
socket.on("battle:sync", handleSync);
socket.on("battle:powerup", handlePowerup);
Enter fullscreen mode Exit fullscreen mode

Consistent naming made debugging much easier when events were flying between two players.

State Synchronization

Keeping two editors in sync with sub-100ms latency required:

  • Redis pub/sub for cross-instance communication
  • Throttling to prevent event flooding
  • State recovery for reconnections
  • Optimistic updates on the client

Race Conditions

What happens when both players submit at the exact same moment? The spec forced me to define this upfront:

## Correctness Properties

- CP-1: Simultaneous submissions must be processed in arrival order
- CP-2: A player cannot submit while their previous submission is being judged
- CP-3: Battle state must remain consistent across disconnection/reconnection
Enter fullscreen mode Exit fullscreen mode

Steering for Consistency

Kiro's steering docs maintained consistency across the codebase:

# coding-standards.md

## TypeScript Guidelines

- Use strict TypeScript configuration
- Prefer interfaces over types for object shapes
- Always define return types for functions

## Backend Conventions

- Controllers handle HTTP/WebSocket requests
- Services contain business logic
- Socket events follow `action:resource` naming
Enter fullscreen mode Exit fullscreen mode

Without steering, Kiro would generate valid but inconsistent code. With steering, every file followed the same patterns—crucial for a full-stack TypeScript project.

What I'd Do Differently

  1. Research deployment constraints first - Check platform limitations before building. My Docker system was elegant but unusable.

  2. Start with the spec - Even for hackathons, 30 minutes of requirements thinking saves hours of rewrites.

  3. Use the CLI - When dashboards fail, CLIs are more reliable.

Try Phantom

🎮 Live Demo: kiroween-phantom.vercel.app

📂 GitHub: github.com/AIEraDev/phantom

Test accounts:

  • player1@test.com / player1@test.com
  • player2@test.com / player2@test.com

Open two browser windows, log in with both accounts, and start a battle to see the real-time sync in action.

Final Thoughts

Kiro's spec-driven development isn't just about generating code—it's about thinking clearly before coding. The structured approach caught edge cases early, made the Judge0 migration possible under pressure, and kept a complex real-time system maintainable.

Phantom started as a hackathon project, but the foundation is solid enough to build on. Next up: tournament brackets, more languages, and maybe ranked seasons.

If you're building something complex with real-time features, give spec-driven development a try. Your future self debugging WebSocket race conditions at 2 AM will thank you.


Built for Kiroween Hackathon 2025 🎃

kiro

Top comments (0)