Saksham: Building an AI Interview Coach

Inspiration

We started with a simple question: what if candidates could practice interviews with real feedback, instantly? Most interview prep platforms feel like they're stuck in 2005 - static questions, no personalization, and definitely no real conversation flow. We wanted to build something that actually understands your resume and adapts to who you are, not the other way around.

The idea crystallized around one insight: your resume is gold data. Instead of asking generic questions like "tell me about yourself," why not let the AI read your actual projects, experience, and skills - then ask you about them? That's when Saksham was born (meaning "capable" in Hindi), with the mission to empower candidates to shine in every interview.

What it does

Saksham is an AI-powered interview practice platform that turns your resume into a personalized coaching experience.

You upload a PDF, the app parses and structures your resume automatically, and then an AI interviewer (powered by Google Gemini 2.5 Flash) conducts a live, streaming interview across three categories: intro questions (warm-up), technical deep-dives, and behavioral scenarios.

After your interview, an admin review queue kicks in - our system generates detailed feedback with per-question analysis, but an actual human admin reviews and releases it to you.

No auto-magic feedback that gets it wrong; real oversight with audit logging for every admin action. Plus, GitHub integration enriches your profile by fetching your actual repos, languages, and README snippets to make questions even more targeted.

How we built it

Architecture: We chose Next.js 14 with the App Router for modern server components, paired with Convex for real-time database subscriptions and serverless functions. Authentication via Clerk handles user sync and admin role management seamlessly.

The interview engine uses a state machine with 7 states: parsing - ready - in_progress - paused - feedback - completed (with abandoned as a terminal state).

This rigid structure prevents invalid transitions - you can't jump from ready straight to completed, and you can't restart after feedback is released.

AI streaming is handled through the Vercel AI SDK with a critical pattern: we use streamText() for real-time tokens to the user, but save the complete message in onFinish callback, never token-by-token to the database. This keeps our write load manageable and prevents message fragmentation.

Resume parsing uses unpdf(not pdf-parse) because native dependencies like canvas break in serverless environments.

After extraction, we send the text to Gemini for structuring into personalized sections: skills, experience, education, projects - each editable before confirmation.

Design philosophy: We embraced neo-brutalism - border-4 border-black, hard offset shadows (shadow-[4px_4px_0_0_#000]), zero rounded corners, bold primary colors, and Space Grotesk/Mono fonts. No gradients, no blur, no soft shadows for just honest, direct UI that feels like an interview tool, not a SaaS dashboard.

Challenges we ran into

Chat state synchronization was our biggest headache. The useChat() hook maintains its own state for streaming, but the source of truth lives in Convex. Without explicit syncing, streamed messages would vanish after onFinish completed.

We solved this with a useEffect that detects new Convex messages and syncs them back into useChat state - small fix, massive impact.

Message ordering nearly broke our admin monitor. Without explicit timestamps in our database index, messages were ordering by Convex's internal _creationTime, which wasn't reliable.

We added a compound index by_interviewId_timestamp and explicitly sorted by the timestamp field. Turns out, database design matters.

PDF parsing in serverless was a rabbit hole. We started with pdf-parse, hit DOMMatrix errors, and spent a day debugging native bindings.

Switching to unpdf solved it, but taught us an important lesson: always check if a dependency works in your deployment target before committing to it.

Category progression logic required careful thinking about when to count a question. We increment question count when the user submits an answer, not when the AI asks a question. This ensures the candidate can answer all N questions before the interview auto-transitions to feedback - off by one errors in interview flow feel terrible.

Accomplishments that we're proud of

Admin oversight with audit logging. Every admin action (stop, resume, edit message, override feedback, release) is logged with timestamps and user IDs. This isn't just nice-to-have; it's essential for trust and accountability.

Settings snapshots. When an interview is created, we snapshot the current interview settings (question counts, category distribution) into the interview record. This means changing global settings doesn't retroactively affect in-progress interviews - users get what they expected.

Neo-brutalist design execution. We stuck to the design philosophy across the entire codebase - no rounded-lg creeping in, no gradient backgrounds, no soft shadows. The UI feels intentional and aggressive (in a good way), like you're doing real work, not playing with an AI toy.

GitHub enrichment pulls real projects, languages, stars, and README snippets to make questions contextual. When the AI asks "tell me about your most-starred project," it actually knows which project has stars and can ask follow-ups on the tech stack extracted from your repository.

Type-safe Convex mutations. We use requireAuth() and requireAdmin() helpers in every protected mutation, with centralized error messages and consistent user context. Zero auth bypasses, zero missing checks.

What we learned

Streaming is a paradigm shift. Token-by-token database writes are a trap - batch on onFinish instead. Your write load will thank you, and your database won't hiccup on high-traffic interviews.

State machines aren't overkill. For interview flows with multiple states, a rigid state machine is not over-engineering. It's the difference between "users can somehow get stuck in an invalid state" and "transitions are guaranteed to be valid."

Serverless constraints shape architecture. PDF parsing, image processing, heavy compute - you need serverless-compatible libraries. Native bindings are fun until your production deployment fails silently.

User context matters. The admin monitor showing real-time messages, the candidate seeing their own interview progress - these aren't nice-to-haves. They're the difference between "I'm talking to an AI" and "I'm being coached by a real person who's watching me."

Audit logging isn't compliance theater. When an admin edits a candidate's feedback, having a record of "who changed what, when" builds trust. It's not about catching bad actors; it's about creating accountability that everyone (admin and candidate) can see.

What's next for Saksham

Coding round with sandboxed execution - let candidates solve actual problems, not just talk about them. We're eyeing a safe execution environment that doesn't let users break anything.

Voice interviews via WebRTC - speech-to-text, real-time transcription, and audio streaming so candidates can interview like it's actually happening. Typing out full answers feels forced, talking feels natural.

Advanced admin features: custom question banks, per-category question difficulty, analytics dashboards showing which questions trip up candidates most. Right now, everything is admin-reviewed; soon, admins will be designing the interview experience by just answering few question.

Multi-user admin roles (v2) - right now, one admin user. Soon, multiple admins with role-based permissions (reviewer, question designer, analytics viewer). As we scale, we need more hands-on deck.

Reaching v1.0.0 is the immediate goal. We're at v0.8.1 with core features solid and audit logging working. Production deployment, real user testing, and iteration based on feedback will get us there.

The journey from a question ("what if interview prep felt real?") to a working platform that handles resume parsing, real-time interviews, and admin oversight has been incredible. We're just getting started.

Built With

  • convex
  • google-gemini-2.5-api
  • nextjs14
  • shadcn
  • tailwindcss
  • vercel-ai-sdk(framework)
Share this project:

Updates