Inspiration
Anyone who has grinded LeetCode before a real interview knows a green checkmark and a passed interview are two different things. A judge tells you your code works. It never asks you to explain your reasoning out loud, pushes back when you skip a step, or tells you which specific moment in your explanation cost you the round. Practicing alone means practicing against silence.
We also kept coming back to a business question: coding platforms and interview prep sites have never been able to sell a real practice interview at scale, because a real practice interview needs a person. Voice models that hold a conversation and language models that can read code well enough to follow along changed that. LarpCode is what that capability looks like when you build it: a Chrome extension that turns any LeetCode problem into a live AI run technical interview.
What it does
You open a LeetCode problem, click Start AI Interview, and a panel docks into the page. From there it plays out close to a real interview. The interviewer asks you to walk through your approach before you touch the editor. You talk, and your mic feeds a live transcript. You start coding, and the extension reads your code straight out of the editor rather than guessing from pixels on screen. When something worth reacting to happens, a finished approach, a slow nested loop, an explanation that trails off, the interviewer asks about it out loud using an ElevenLabs voice, grounded in your actual code and your actual words. You can ask for a hint, and hints escalate in tiers instead of handing you the answer on the first ask.
At the end you get a review built from what happened in that session: a score, a breakdown by category like clarifying questions, approach, and communication, and specific strengths and gaps tied to something you actually said or wrote. Communication and reasoning are scored on their own terms, so a candidate who explains their thinking clearly but doesn't finish the code still gets a useful, honest read on where they stand.
How we built it
The extension (Chrome Manifest V3, React, TypeScript, Tailwind) owns the UI and media capture. Because a content script can't see LeetCode's editor object directly, we inject a small script into the page's own execution context that reads the live Monaco model and posts it back, with a DOM scrape as a fallback if that bridge isn't there. A FastAPI backend owns interview state and the AI side.
The default interview experience runs on a single hosted ElevenLabs Conversational AI agent that handles speech to text, the interviewer's turn taking, and the voice together over one connection, which is what makes the conversation feel live rather than turn based. A separate evaluator agent, built on Anthropic Claude, only runs once at the end, reading the full session record to write the review. It's only allowed to cite evidence that actually exists in that session's transcript, code snapshots, and hint history, so a citation that doesn't resolve gets rejected and retried rather than shipped as a plausible sounding guess.
Every provider integration sits behind a real implementation and a mock one selected by a single flag, so the whole flow runs and demos end to end with zero API keys. The backend is deployed on a small VPS behind nginx, gated by a shared join code, since interview sessions live in memory on one process by design.
Challenges we ran into
The websocket connection worked perfectly in every test and never connected on a real LeetCode page, with nothing useful in the browser console to explain why. The fix came from treating the backend as the observer: running it with debug logging showed zero inbound connection attempts from the page, while a probe from the extension's own background context connected instantly. LeetCode's page level security policy was blocking the page context from opening the connection at all, so the socket moved to live in the extension's background worker instead of the content script.
We also found a privacy bug the hard way: closing an interview mid session on a LeetCode page navigation left the microphone recording, because our teardown removed the UI from the page without ever unmounting the React root that owned it. Nothing errored or logged, the UI just quietly kept telling you the mic was off while it wasn't. Given the whole product depends on that indicator being honest, we fixed the root cause and layered several independent, deliberately redundant safeguards on top rather than trusting a single fix.
Getting the interviewer to stay quiet most of the time was its own problem. Reacting to every keystroke is cheap to build and feels nothing like being interviewed by a person, so pacing is enforced by a separate deterministic layer, cooldowns, debouncing, and a check against interrupting mid sentence, sitting outside the model rather than inside its prompt.
Accomplishments that we're proud of
Getting an ElevenLabs Conversational AI agent tuned to actually hold interview pacing, not just answer questions, so it knows when to stay quiet as much as when to speak. Catching and fully closing a live privacy leak before it ever reached a demo. A final review that has to point at real evidence instead of writing something generic that could describe any candidate. And a build that runs completely offline against mock providers, so we could develop and demo the whole loop before a single real API key was ever needed.
What we learned
A mock provider proves your control flow works, not that your integration does. The first real run against live speech to text surfaced three separate bugs that months of mock testing never would have. We also learned that a feature can be exactly what the spec called for and still feel wrong once it's in front of you, an early live score ticking up mid interview matched what we'd planned, but it felt punishing rather than useful, so we moved it behind the final review and kept the data underneath unchanged.
What's next for LarpCode
The next step is a full rehearsal with real provider keys and a real microphone, since several pieces have only been verified against mocks so far. Past that, we want the interview to survive a dropped connection without losing live transcription, and to run against a persistent database instead of only in memory.
The bigger direction is turning this into something other platforms plug in rather than something only we run. The code analysis layer already separates a language specific pass from a general one, so supporting more languages and more coding platforms is an extension, not a rebuild. The real pitch is a licensable interview layer that any coding platform or bootcamp can offer their own users as a paid feature, giving people realistic, live interview practice with an interviewer that listens as closely to how they think as it does to whether their code runs.
Built With
TypeScript, React, Vite, Chrome Extension Manifest V3, Tailwind CSS, Zod, Python, FastAPI, WebSockets, Pydantic, Anthropic Claude, ElevenLabs Conversational AI, ElevenLabs, Deepgram, PostgreSQL, Supabase, Docker, nginx, GitHub Actions
Built With
- chrome
- claude
- docker
- elevenlabs
- extensions
- fastapi
- leetcode
- pydantic
- python
- tailwindcss
- typescript
- websocket
- zod
Log in or sign up for Devpost to join the conversation.