Most difficult front-end work is difficult because of scale, or state, or a browser disagreeing with a specification. The live interview surface at ReadyFly was difficult for a different reason. Three subsystems that each behave as though they own the tab were running concurrently, while someone's job application happened in real time.

Real-time video over LiveKit, using WebRTC. An embedded Monaco editor, because the assessment is a coding one. And proctoring, observing continuously throughout. One session, one browser, one main thread.

I made the first commit to that web application and went on to author roughly three quarters of it, 157 of 209 commits, so this surface is mine to explain, including the part I got the wrong way round first.

The stakes change what "good enough" means

A dashboard that stutters is annoying. A messaging client that drops a frame is unremarkable. This is neither.

If the editor lags behind a candidate's typing, they lose their place in a problem they are being judged on. If video degrades while they are explaining their reasoning, an interviewer forms an impression of hesitancy that belongs to the network. If proctoring interferes with either, the product has damaged the assessment it exists to conduct.

So the usual framing, degradation as a percentile on a chart, does not survive contact with this feature. A stutter here is a bad interview that somebody will remember for years, and the candidate has no way of telling a hard question from a slow tab.

That reframing is most of the engineering. Once you accept that the acceptable failure rate is close to zero for the duration of a session, you stop asking how to make the surface fast and start asking what the three subsystems are competing for.

What they are competing for

They compete for the two scarce things a browser tab has: the main thread, and a network path.

Each subsystem is built on the reasonable assumption that it is the important thing on the page. A real-time media stack wants a steady, uninterrupted cadence, and it reads a busy main thread as a network condition, so it degrades quality in response to something that has nothing to do with the network. A code editor wants to respond inside a keystroke, which means it wants the main thread at exactly the unpredictable moments a person decides to type. Proctoring is continuous rather than bursty, which makes it the easiest of the three to underestimate. A small constant cost looks like nothing next to two large intermittent ones.

None of those assumptions is wrong on its own. They cannot all hold at once, and the work is deciding which one yields and when. That is a design question about the product more than a performance question about the code. During an interview, the candidate's typing and the media stream are the interview, and everything else is negotiable around them.

I would rather describe the shape of that problem than publish specifics of how a private codebase resolved it. The transferable lesson is that when you combine three libraries each documented for solo use, the integration is the feature. Reading their individual documentation tells you nothing about what happens when they meet.

The loading decision I had backwards

The one I would put in front of a reviewer is a mistake, because it has a clean shape.

A WebRTC library is large. It was being loaded eagerly, which is the default and feels harmless: get it ready, so it is there when the session starts. But only a small fraction of page views are a live interview. Every recruiter opening a dashboard, every candidate browsing roles, every administrator checking a listing was paying to download a real-time media stack they would never instantiate.

So I split LiveKit into an asynchronous chunk, loaded on demand, and expanded tree-shaking around it.

The ratio is what makes this the right decision rather than a micro-optimisation, and it is the same ratio I have argued about elsewhere: the cost of a feature is paid by every session, while the benefit is often paid to a fraction of them. Eager loading takes a cost that belongs to a few sessions and charges it to all of them. The interview surface did not get slower when the library arrived at the moment it was needed. Everything that was not an interview got faster.

The eager version was not a bug and nothing was broken. It was a default that nobody had made a decision about, which is the category of thing I now look for first.

Parsing the CV in the browser

The other piece of that application I would point at is quieter. The CV pipeline front end handles upload, parses the PDF in the browser with pdf.js, and produces the structured candidate profile.

The obvious architecture sends the file to the server and parses it there. Doing it client-side is more awkward. You are running a parser in a tab, on a document of unknown provenance and questionable structure, and CVs are the worst-formatted documents in professional life.

It is worth the awkwardness because of what the candidate experiences. They chose a file three seconds ago. Parsing in the browser means they find out immediately what was read out of it, while the document is still the thing they are thinking about and choosing a different one is trivial. Parse on the server and the feedback arrives after a submit, in a different mental context, about a decision they have already put behind them. The failure that matters there is a candidate who quietly abandons a half-built profile, and a parse error is only the visible part of it.

My belief is that this materially changes the completion rate of that flow. I want to be careful about the word "belief". I do not have a measured before-and-after figure for it, and the argument above is reasoning from how the interaction feels. It would be an easy thing to measure and I did not measure it.

The rest of it, and the boundary

The application covers four distinct experiences in one codebase, for candidates, recruiters, hiring companies and administrators. It is built in Next.js with TypeScript and Redux Toolkit, alongside authentication, the marketing site, interview scheduling, messaging, notifications, company and job management, and progressive web app support. Four audiences in one product is its own kind of difficulty. The shared surface is large enough that a change made for recruiters lands in front of candidates unless someone is deliberately holding the four models apart.

On the Python and FastAPI side I was a contributor rather than a principal author: 31 of 209 commits, across services, the API surface, domain models, routing, utilities and the security layer. The substantive piece was implementing the microservice decomposition that separated CV ingestion, authentication and profile services out of the monolith. I also added WebSocket support and worked on the conversational assistant's behaviours.

Then the boundary, which is the thing most worth being precise about on any product with AI in it: the models were not mine. Semantic matching and candidate evaluation were owned by the team's AI engineer. I built the product layer they were surfaced through and contributed to the services around them. "I worked on an AI recruitment platform" is one of the easiest sentences in this industry to say and one of the least informative. Blurring it is how a candidate loses credibility in the first ten minutes of a technical conversation.

What I can and cannot evidence here

For this role I have commit shares and nothing better. No blame analysis, no surviving-authorship figure. It was a part-time, equity-based founding role, and commit share is the weakest of the three measurements I use for exactly this problem. When it is all you have, the right response is to name it as the weakest rather than present it in the same voice as something stronger.

So: principal author of the web application, contributor to the API, and I did not build the models. The interview surface is in my evidence because it is the most demanding browser engineering I have done. The split between those two repositories is also a fair picture of how I work in a team where I am not the only engineer. I am the principal author where I own the surface and a useful contributor elsewhere, and I say which is which.


Full scope and ownership breakdown: ReadyFly.