Skip to content

LiveAug 2025 — present

TandemCode

Practice coding problems with a partner.

01The problem

As an underclassman it's hard to find peers who want to do mock interviews or practice DSA in a pair. TandemCode is a place to find a partner at your level and actually practice together.

The bigger goal: make interview prep social and consistent, with the accountability of doing it with someone else.

02What I built

One shared editor, one judge, and a replay of the whole session.

  1. 01

    Rooms

    Public or unlisted, shared by invite link, with a way to ask for a partner that highlights the room on the rooms page.

  2. 02

    One shared editor

    Live cursors on a Yjs CRDT. The server doesn't hold a copy of the document; it relays frames between peers and records changes for the replay.

  3. 03

    A real judge

    Problems ship with starter code, visible examples and hidden tests. Python submissions are judged in an isolated container per run, and everyone in the room sees the verdict.

  4. 04

    Complexity estimates

    An accepted run can be rerun on growing inputs to estimate its time complexity, measured as CPU time from outside the program and fitted to a power of n.

  5. 05

    Replay

    The code as it was typed, the chat and every run, played back after the room closes.

  6. 06

    Problems by pull request

    Each problem is a SQL migration, so contributing one is a PR.

03Architecture

Five parts: a React single-page app on Vercel; a FastAPI process serving HTTP under /api and two websockets under /ws; a separate runner process from the same image that judges submissions; Postgres for everything (users, rooms, chat, editor history, problems, submissions); and Clerk for sign-in, so TandemCode stores no passwords.

Routes → services → DAOs. Services hold the rules (who can read a room, how many runs per hour); DAOs hold hand-written SQL for asyncpg. Rate limits that must hold under parallel requests are enforced inside a transaction, not by counting first and inserting later.

A run is queued as a pending submission; the runner claims it, judges it and stores the verdict, which fires a Postgres NOTIFY that the API broadcasts to the room. Fan-out to many sockets sends in parallel with a deadline so one stalled peer can't freeze a room.

Every submission is treated as hostile: a fresh container with no network, a read-only root filesystem, an unprivileged user with every capability dropped, memory and process caps, and a hard time budget. In production each container runs under gVisor, so escaping takes a gVisor bug and a kernel bug.

Production is one Linux host running Docker Compose behind Caddy, with the web app on Vercel.

04Challenges & lessons

Learning a new stack is all about momentum

This was very technically challenging, but it kept me engaged. Planning exactly what I'd do week by week, fixing scope and getting all the setup out of the way made the whole thing smooth. I've quit projects a day or two in before; front-loading planning and setup is what changed that.

Challenge: four security reviews before deploy

The September 2026 rebuild went through four security reviews before deploy: bounding every websocket send, capping sockets per user without races, limiting editor frames, and only letting people who've been in a room read its runs, code and roster.

Challenge: timing code inside a sandbox

Under gVisor the fixed cost of a run is about 150 ms, ten times a laptop's, and CPU time comes in 10 ms steps. Complexity estimates only count sizes where the solution's own time beats that floor, and short runs are timed twice with the faster kept.

05Version 1: Spring Boot

The first TandemCode was React on the front, Java Spring Boot on the back, Docker + Postgres for data, WebSockets for real-time and Clerk for auth. The plan then: real-time pair programming with CRDT/OT, time and space complexity analysis via code execution in ephemeral containers, AI interview feedback, and a social layer for finding peers at the same level.

Spring Boot felt like magic: clean defaults, auto-configuration and a plug-and-play ecosystem that hides enterprise Java's boilerplate, so you can focus on logic instead of plumbing. The 2026 rewrite moved to FastAPI and shipped the CRDT editor and the containerized judge from that plan.

06Screens

  • The mascot, which is also the README logo
    The mascot, which is also the README logo