Online Interview Assessment System
A browser-based technical-interview platform: live video, a collaborative code editor that runs 13 languages, chat, scheduling, and QR-based candidate join - all in one room.

- Role
- Solo full-stack
- Scope
- Video + editor + scheduling
- Real-time
- WebRTC · WebSockets · REST
- Languages
- 13 executable
TL;DR
A Google-Meet-style platform purpose-built for technical interviews: interviewer and candidate join a live video room, write and run code together in real time across 13 languages, chat, share screens, and keep a searchable record of every interview. The hard parts all live in the real-time layer: peer-to-peer video over WebRTC, character-by-character code sync over WebSockets, and bridging two auth systems (Firebase + JWT) into one trusted identity.
The problem
Built during the COVID-19 pandemic, when technical interviews moved fully remote overnight. Interviewers were stitching together a video call, a shared coding pad, and an email thread to assess a single candidate - constant context-switching, and nothing tied the who, when, code, and outcome of an interview together.
Remote technical interviews meant juggling three tools - a video call, a shared coding pad, and a calendar thread - with nothing tying the who, when, code, and outcome together. The goal: collapse all of it into one web app, no installs.
The result
Built solo, end to end - one room that replaces a video call, a shared coding pad, and a scheduling thread. Three real-time transports (WebRTC, WebSockets, REST) coordinated so media stays peer-to-peer and the server is a matchmaker, not a bottleneck.
Role & ownership
Sole developer - full-stack, design, and infrastructure. Owned the React SPA, the Express + Socket.IO server, the WebRTC mesh, the MongoDB schema, the Firebase-to-JWT auth bridge, and deployment.
System at a glance
Three independent transports run at once. REST (Express) handles stateful CRUD - auth, interview records, schedules. A Socket.IO server, attached to the same Node HTTP process, carries low-latency room state: presence, chat, live code sync, and WebRTC signaling. WebRTC then carries the actual audio/video peer-to-peer, never through the server. MongoDB Atlas is the source of truth; Firebase handles the Google sign-in experience; an external compiler API runs submitted code.
Single Node HTTP server
The real-time engine
The interview room ties three real-time mechanisms together at once - peer-to-peer video, character-by-character code sync, and live code execution - each on the transport that suits it.
Video - WebRTC mesh
Direct peer-to-peer audio/video between everyone in the room, via simple-peer.
- On join, the server hands you the list of existing peers; you open an initiator connection to each
- SDP offers/answers and ICE candidates are exchanged over the socket relay
- New joiners are added incrementally; leavers trigger peer teardown
Engineering highlights
- Media never touches the server: it's a matchmaker for the handshake only, so it never becomes a bandwidth bottleneck.
- Right-sized topology: a full mesh is ideal for the 2-4 people in an interview, with no media server to run.
Live code sync - Socket.IO
Both editors stay in lockstep as either side types.
- The CodeMirror editor emits CODE_CHANGE on every keystroke
- The server broadcasts it to the rest of the room, which applies it
- A late joiner triggers SYNC_CODE and is replayed the current buffer
Engineering highlights
- Echo-loop guard: programmatic setValue is ignored, so an applied remote change doesn't re-broadcast and ping-pong forever.
- No blank-editor race: replaying the buffer to late joiners means everyone converges on the same code regardless of join order.
Multi-language execution
Run the candidate's code in 13 languages, in front of both people.
- C, C++, Java, Python, C#, Go, JS, Node, PHP, Perl, TypeScript, Kotlin, Ruby - each with a starter stub
- Run ships code + stdin to an external compiler API
- Output / error / status is synced back through the same socket channel
Engineering highlights
- No untrusted code on my server: execution is offloaded, so there's no sandbox to maintain - at the cost of a third-party dependency.
- Shared result: interviewer and candidate see the same run output at the same moment.
Live sync
Run code
Under the hood
Full-stack, built solo: a React SPA, an Express + Socket.IO server sharing one process, and a MongoDB document model with per-record ownership.
Frontend
React 18 · Tailwind + MUI · Framer Motion · CodeMirror · simple-peer
- Actions.js holds the socket event names both client and server import - one source of truth for the protocol.
- Auth lives in React context; WebRTC peers + the socket sit in refs so they survive re-renders.
- Tailwind for bespoke layout, MUI/Syncfusion for the heavy widgets (grids, scheduler).
Backend
Node · Express · Socket.IO · JWT · bcrypt · Nodemailer · Passport
- One HTTP server hosts both REST and WebSocket - a single deploy target serving two protocols.
- In-memory maps track socket to username and room membership for presence.
- fetchuser middleware verifies the JWT and scopes every record to its owner.
Database
MongoDB Atlas · Mongoose · 5 collections
- Soft-delete: records flip isDeleted into a recoverable bin before any hard delete.
- db.js retries the connection up to 3x so a transient Atlas hiccup at boot won’t kill the server.
- Queries always include authorId, so users can’t fetch each other’s records by ID.
REST API surface
- /api/authregister · login · google · password reset · account deletion · schedule-interview
- /api/notesCRUD interview records · status filter · soft-delete bin · case-insensitive search
- /api/schedulecreate · list · delete planned interview slots
The auth bridge
Two auth worlds had to be reconciled. Firebase gives a great login experience (one-click Google) but its identity is only trusted on the client; the Express API has to trust requests on its own terms. The bridge keeps both - Firebase owns the login UX, a backend JWT is the actual key the server trusts.
There's also a full classic path for non-Google users, with production-grade lifecycle features rarely seen in a student project:
- Email/password with bcrypt (salt rounds = 10) and express-validator input rules, issuing the same JWT.
- Forgot/reset password via an emailed, single-use UUID token with same-day expiry (Nodemailer).
- Account deletion as a two-step, email-confirmed flow that cascades and removes the user's interview records.
Technical challenges & decisions
- 1
Bridged Firebase auth to a backend JWT
Firebase gave one-click Google sign-in but its identity is only trusted client-side. On login the client posts to /api/auth/google; the server upserts a MongoDB user and mints its own JWT as the real trust anchor. Decouples the backend from Firebase - the tradeoff is /google trusts the posted email (prod should verify the Firebase ID token).
- 2
WebRTC mesh over an SFU
Each participant opens a direct peer connection to every other; Socket.IO relays only the SDP/ICE handshake. Media never touches the server. Perfect for 2-4 person interviews; a mesh would need an SFU to scale past that.
- 3
Keystroke-diff code sync with a late-joiner replay
The editor broadcasts CODE_CHANGE over Socket.IO, guarding against echo loops by ignoring programmatic setValue. A late joiner fires SYNC_CODE and is replayed the current buffer so it never starts blank. Lightweight - not conflict-free (no CRDT/OT yet).
- 4
Offloaded code execution to an external compiler API
Running untrusted code across 13 languages on my own server is a sandbox I did not want to maintain. The run request ships code + stdin to a compiler API and syncs the result back through the same socket, so both sides see the output simultaneously.
- 5
A single shared event-name contract (Actions.js)
Socket event names (JOIN, CODE_CHANGE, SYNC_CODE, DISCONNECTED) live in one module imported by both client and server. Renaming an event can no longer silently desync the two halves of the realtime protocol.
- 6
Three transports in one Node process
Express (REST) and Socket.IO (WebSocket) share a single HTTP server, while WebRTC media stays peer-to-peer. One deploy target, two protocols served, and the server never relays video - so it is a matchmaker, not a bottleneck.
- 7
Soft-delete by default
Interview records flip an isDeleted flag into a recoverable bin rather than being destroyed; a separate action hard-deletes. Undo-friendly UX at the cost of a little query complexity.
- 8
Resilient DB connect + per-record ownership
db.js retries the Mongo connection up to 3 times so a transient Atlas hiccup at boot does not kill the server. Every list/search query is scoped to authorId === req.user.id, so a user physically cannot read another user’s records by ID manipulation.
- 9
Production-grade account lifecycle
Password reset and account deletion run on emailed, single-use UUID tokens with expiry (Nodemailer); deletion is a two-step, email-confirmed flow that cascades and removes the user’s interview records. Rare in a student project, and the right default.
What I'd do differently
This is an older solo codebase that works end to end. Being able to critique my own system is part of the story - here is the path from “student project that works” to a production service.
- Secrets management: move all config to environment variables / a secrets manager and rotate anything that ever lived in the repo - the first thing I'd fix.
- Verify Firebase ID tokens server-side with the Admin SDK instead of trusting the posted email on
/api/auth/google. - JWT expiry + refresh, and move tokens out of sessionStorage toward httpOnly cookies.
- Swap the mesh for an SFU (mediasoup / LiveKit) the moment group interviews need more than ~4 people.
- Conflict-free code sync with a CRDT (Yjs) or OT so simultaneous typing can't clobber.
- Centralize config (hardcoded localhost URLs become env-driven) and add the test suite it currently lacks.
- TypeScript end to end - the realtime protocol is exactly the kind of contract that benefits from static types.
What this demonstrates
Real-time systems thinking
Coordinating WebRTC, WebSockets, and REST without them stepping on each other.
Full-stack ownership
From a marketing landing page to JWT middleware to schema design - built solo.
Integration over reinvention
Knowing when to lean on Firebase, a compiler API, or an SFU instead of building everything.
Product instinct
Soft-delete bins, QR join, scheduling, email flows - features that come from thinking about the user.
Engineering maturity
Shipping something that works and articulating exactly how I'd harden it for production.
Tech stack & tools
- React
- JavaScript
- Tailwind CSS
- Material UI
- Framer Motion
- WebRTC
- Socket.io
- CodeMirror
- Node.js
- Express
- MongoDB
- Mongoose
- Firebase
- JWT
- Passport
Have a product that needs building?
I help founders ship full-stack products - from the first architecture decision to production. Currently available for freelance work.