Skip to content

Repository files navigation

Evalio

Real-time AI interview simulator. Practice technical and behavioral interviews with voice, code, and system design — with an AI that adapts to your target company, role, and preferred style.

Evalio platform screenshot

Features

Area Capabilities
Voice Interview Real-time bidirectional audio, AI can interrupt, incremental live transcription
DSA Coding LeetCode-style problems, in-browser editor (Python/C++/TS), event-driven code review
System Design Excalidraw whiteboard, AI reads your diagrams, canvas suggestions via structured markers
Company Profiles 16 companies with culture, interviewer behavior, and role-specific topics
Interview Styles Supportive, Professional, Challenging, Bar Raiser — each with different interruption and probing patterns
Depth Levels Standard, Probing, Challenge, Bar Raiser — control how many follow-ups and how hard
Rounds Company-specific round selection (Phone Screen, Technical Deep Dive, System Design, Behavioral)
Resume & GitHub Upload resume or link GitHub for personalized, targeted questions
Evaluation Post-interview AI scoring across 6 dimensions, skill profile tracking over time
Queue System Redis-backed FIFO queue when session limit is reached, real-time position updates

Architecture

ai-interview/
├── apps/
│   ├── backend/          # Elysia.js HTTP server + WebSocket server
│   │   ├── src/
│   │   │   ├── prompt.ts         # Dynamic prompt assembly engine
│   │   │   ├── gemini.ts         # AI session wrapper (model-agnostic interface)
│   │   │   ├── ws/
│   │   │   │   ├── index.ts      # WebSocket server entry
│   │   │   │   ├── session.ts    # InterviewConnection class
│   │   │   │   ├── dedup.ts      # Transcription deduplication
│   │   │   │   └── finalize.ts   # Post-interview finalization
│   │   │   ├── lib/
│   │   │   │   ├── redis.ts      # Redis client (queue backend)
│   │   │   │   ├── queue.ts      # Queue helpers (tryActivate, enqueue, dequeue)
│   │   │   │   └── email.ts      # Email templates (OTP, welcome, feedback)
│   │   │   └── routes/           # Auth, user, resume, interview, feedback, pricing
│   │   └── prisma/               # PostgreSQL schema + migrations
│   └── frontend/                 # React 19 SPA
│       └── src/
│           ├── pages/            # NewInterview, Interview, Results, Dashboard, Feedback, Pricing
│           ├── components/       # CompanyGrid, RolePicker, SessionControls, InterviewQueue, Landing*
│           ├── hooks/            # useMicrophone, useAudioPlayer
│           └── lib/              # WebSocket client, API client, auth
├── packages/
│   ├── shared/                   # Zod schemas, shared types, company configs
│   ├── ui/                       # Shared UI primitives
│   ├── eslint-config/
│   └── typescript-config/

Features

Voice Interview Engine

  • Real-time bidirectional audio via WebSocket + AI audio API
  • AI can interrupt mid-answer when answers go off-track
  • User cannot interrupt AI — mic blocked during AI speech
  • Incremental transcription with character-level deduplication

16 Company Profiles

Each company has structured culture, interviewer behavior, and role-specific interview data:

Company Style Depth Roles
Stripe Challenging Challenge Backend, Payments, Platform
Amazon Bar Raiser Challenge SDE, PM, Solutions Architect
Google Professional Probing SWE, Data Scientist, UX Engineer
Meta Challenging Probing Frontend, ML, Infrastructure
Netflix Bar Raiser Bar Raiser Backend, Data, SRE
Microsoft Professional Standard SWE, DevOps, AI Engineer
Apple Challenging Probing iOS, Hardware, Security
Uber Professional Challenge Backend, Mobile, Data Science
Airbnb Supportive Probing Fullstack, Design, Staff
Datadog Professional Standard SRE, Cloud, Support
Deloitte Professional Probing Consultant, Data Analyst, Cloud
Goldman Sachs Bar Raiser Challenge Quant Dev, Risk, Platform
Palantir Challenging Bar Raiser FDE, Data, Security
Figma Supportive Standard Design Engineer, Frontend, Platform
Notion Supportive Standard Fullstack, Mobile, Infra
Startup Supportive Challenge CTO, Founder, Staff

Roles define topics, evaluationCriteria, and mustProbe — so a Stripe Backend interview (distributed systems, APIs, caching) is completely different from a Stripe PM interview (prioritization, metrics, stakeholder management) even with the same style and depth.

4 Interview Styles

Controls how questions are asked:

Style Approach Interruption
Supportive Conversational, encouraging Rare, gentle redirection
Professional Structured, neutral When unfocused or repetitive
Challenging High-pressure, push for depth Aggressive, cut off off-track answers
Bar Raiser Elite, surgical Strategic — highest leverage point only

4 Interaction Depths

Controls how many follow-ups and how hard each question is probed:

Depth Follow-ups Challenge Level
Standard None Smooth, conversational
Probing 1-2 per topic Gentle elaboration requests
Challenge Until defended Disagree, demand metrics
Bar Raiser Maximum rigor Skepticism, evidence required

Interview Round Selection

  • 4 unique rounds per company (e.g., Stripe: Phone Screen, Technical Deep Dive, System Design, Leadership & Behavior)
  • Custom round input for roles not listed
  • Progress stepper showing step position in setup flow

Prompt Assembly Engine

Instead of a single static prompt, the system assembles a dynamic prompt from layers:

Interview Objective           ← Optimize for signal, not coverage
Candidate History             ← Past scores, strengths, weaknesses
Company Context               ← Culture + Interviewer Approach
Role Context                  ← Topics + Evaluation Criteria + Must Probe
Interview Style               ← How to ask
Interaction Depth             ← How many follow-ups
Resume / GitHub / JD          ← Personalization data
Evaluation Dimensions         ← What to assess (6 dimensions)
Story Extraction              ← Identify reusable stories
Interview Guidelines          ← Practical rules

Impact weighting: Role ~60% (drives what gets asked), Style ~25% (how), Company ~10% (cultural emphasis), Depth ~5% (follow-up count).

Evaluation & Scoring

  • Post-interview evaluation via AI
  • Per-turn scoring with feedback
  • 6 dimension scores: Communication, Technical Depth, Problem Solving, Leadership, Ownership, Decision Making
  • Resume-strength correlation
  • Candidate skill profile updates over time
  • Candidates can retake interviews after 7 days (FREE tier: 3/week)

Queue Management System

  • Redis-backed FIFO queue when concurrent session limit is reached
  • MAX_CONCURRENT_SESSIONS=4 (configurable)
  • Real-time position updates pushed to waiting clients
  • Heartbeat (30s ping / 10s timeout) to detect stale connections
  • Automatic slot release and dequeue when an interview ends

Feedback System

  • Premium editorial feedback form with Tabler-style icons and progressive bar rating
  • Categories: Bug Report, Feature Request, Performance, UX, Other
  • Admin dashboard for reviewing all feedback
  • Automated thank-you email via Resend

Pricing & Rate Limits

  • FREE tier: 3 interviews / 7 days, 15 min cap
  • PRO tier: 6 interviews / 7 days, 30 min cap (contact for upgrade)
  • Max tier: Coming soon
  • Custom toast notifications for rate limit errors with upgrade links

Real-time Audio Pipeline

Browser Mic → PCM 16kHz → WebSocket → AI Audio API → Audio + Transcription → Browser Speaker
     │                           │
     └── audio_stream_end ───────┘

DSA Coding Round (Coming Soon)

  • LeetCode-style problems sourced from company-specific question data
  • 1900+ companies with real historical question frequency data
  • 6-phase interview: Understanding → Brute Force → Optimization → Implementation → Testing → Review
  • Event-driven code review (no auto-snapshots, AI reviews on request only)
  • Hidden rubric per question guides AI evaluation
  • Monaco Editor in-browser with Python/C++/TypeScript support
  • 25 min fixed timer with phase tracking

Additional Features

  • Resume upload & analysis — PDF parsing, section detection, AI-tailored questions
  • GitHub integration — public repo analysis for code-specific questions
  • Job description parsing — paste a JD for targeted questions
  • Custom company & role — AI generates interview context on the fly
  • Email verification — OTP via Resend
  • Interview history — dashboard with scores, feedback, improvement tracking
  • Role-based access control — FREE, PRO, ADMIN tiers with different limits

Tech Stack

Layer Technology
Runtime Bun 1.3+
Backend Elysia.js
Frontend React 19, motion (animations)
Database PostgreSQL + Prisma
AI Multi-model (Gemini, more coming)
Real-time WebSocket (ws)
Queuing Redis
Auth JWT + OTP
Email Resend
CSS Tailwind CSS 4
Icons react-icons, Tabler Icons
Monorepo Turborepo

Quick Start

Prerequisites

  • Bun 1.3+ (curl -fsSL https://bun.sh/install | bash)
  • PostgreSQL running locally or remotely
  • Redis running locally or remotely (for queue system)
  • AI API key (Gemini or compatible)

Setup

# Install dependencies
bun install

# Copy environment files
cp apps/backend/.env.example apps/backend/.env
cp apps/frontend/.env.example apps/frontend/.env

# Set up your .env files
# apps/backend/.env requires:
#   DATABASE_URL=postgresql://...
#   AI_API_KEY=your_key
#   JWT_SECRET=...
#   RESEND_API_KEY=...
#   REDIS_HOST=localhost
#   WS_PORT=8080
#   MAX_CONCURRENT_SESSIONS=4
#
# apps/frontend/.env requires:
#   VITE_API_HOST=http://localhost:3000
#   VITE_WS_HOST=localhost:8080

# Run database migrations
bun run --filter @evalio/db prisma migrate dev

# Start development servers
bun run dev

Access

Service URL
Frontend http://localhost:5173
API http://localhost:3000
WebSocket ws://localhost:8080

Commands

Command Description
bun run dev Start all apps in development mode (hot reload)
bun run build Build all apps and packages
bun run lint Run ESLint across all packages
bun run check-types Run TypeScript type checking
bun run format Format code with Prettier
bun run --filter @evalio/backend dev Backend only
bun run --filter @evalio/frontend dev Frontend only

API Overview

HTTP Routes

Method Path Description
POST /api/auth/signup Register with email + password
POST /api/auth/verify-otp Verify email with OTP
POST /api/auth/login Login, receive JWT
GET /api/interviews List user's interviews
POST /api/interviews Create new interview
GET /api/interviews/:id Get interview details
POST /api/resumes/upload Upload resume (PDF)
GET /api/github/profile Get linked GitHub profile
POST /api/companies/generate AI-generate custom company context
POST /api/feedback/submit Submit feedback
GET /api/feedback List feedbacks (admin only)

WebSocket Messages

Direction Type Purpose
Client → init Authenticate, start interview session
Client → audio_chunk Send PCM audio data
Client → audio_stream_end Signal end of user speech
Client → end_interview Request closing + evaluation
Server → ready Interview initialized, listening
Server → serverContent AI speech (audio + transcription)
Server → queued Session queued (position in queue)
Server → position_update Queue position changed
Server → closing_started Interview entering closing phase
Server → feedback_ready Evaluation complete, navigate to results
Server → time_limit Total interview duration
Server → time_warning 1 minute remaining
Server → time_limit_reached Time expired, closing triggered

Key Design Decisions

Why Structured Company/Role Data Instead of Free-form Personality

Free-form personality strings caused the AI to produce similar interviews across roles at the same company. By splitting into culture (company values), interviewerBehavior (approach), and role-specific topics, evaluationCriteria, and mustProbe, the generated interview varies meaningfully by role. Impact: Role ~60%, Style ~25%, Company ~10%, Depth ~5%.

Challenge Mode Turn Accumulation

In standard interview modes, each Q&A creates a new database turn. In Challenge and Bar Raiser modes, multiple user answers accumulate into a single turn until the AI asks a new question (detected via isNewQuestion() heuristic). This gives the AI room to challenge, probe, and redirect without creating spurious turn boundaries.

AI Interruption Without Backend Round-trip

Interruption is detected on the frontend: when AI sends audio while the user is speaking, the mic is stopped immediately and audio_stream_end is sent. No backend round-trip needed, keeping latency low.

Redis Queue for Session Management

Instead of Kafka or PostgreSQL-based queuing, the system uses Redis Sorted Sets for FIFO queuing and Redis Sets for tracking active sessions. This provides O(log N) queue operations and real-time position updates without the overhead of a full message broker.

Event-Driven Code Snapshots (DSA)

DSA rounds use event-driven code snapshots instead of auto-snapshots every 10 seconds. Code is sent only when the user requests review, clicks Run, says "I'm done," or the AI asks to see the current implementation — reducing storage and token cost by ~95%.

Prompt Layering

Instead of one monolithic system prompt, the prompt is assembled from independent sections (Objective, Company Context, Role Context, Style, Depth, Resume, Guidelines). Each section is independently maintainable and conditionally included. This makes it easy to tweak individual behaviors without affecting the rest.

Benchmarks

All benchmarks run on Apple Silicon M3, 16GB RAM, macOS, against PostgreSQL 16 on localhost with the backend on port 3000.

Database Query Performance

Run with EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) via PostgreSQL 16.

Query Planning Execution Total Scan Buffers
Interview list (userId + createdAt sort, LIMIT 21) 4.01 ms 1.11 ms 5.13 ms Index Scan 8 hit
Rate-limit count (7-day window per user) 0.60 ms 0.34 ms 0.93 ms Index Scan 2 hit
Score trend (last 5 completed, scored) 0.28 ms 0.01 ms 0.29 ms Index Scan 2 hit
Interview detail (with summary + resume joins) 1.13 ms 0.04 ms 1.16 ms Index Scan 2 hit
Refresh token lookup (by hash) 0.99 ms 0.02 ms 1.01 ms Index Scan 1 hit
Composite average 1.70 ms

Indexes installed:

  • InterviewSession(userId, createdAt) — covers list + rate-limit queries
  • InterviewSession(userId, status, createdAt) — covers score-trend queries
  • RefreshToken(tokenHash) — unique index for O(1) token rotation lookups

Estimated improvement vs unindexed (10k rows): ~29–117× faster (sequential scan ~50–200ms → index-only scan ~0.1–2ms)

API Latency & Peak Load

Tested with fetch() under sequential and concurrent load. Auth endpoint returns 401 (expected — no token), counted as success since latency and throughput are the measure.

Per-Endpoint Latency (20 requests each)

Endpoint Avg P50 P95 P99 Success
GET /health 0.2 ms 0.1 ms 0.7 ms 0.7 ms 100%
GET /ready 0.1 ms 0.1 ms 0.3 ms 0.3 ms 100%
GET /api/auth/me 0.2 ms 0.2 ms 0.5 ms 0.5 ms 100%*

*Returns 401 as expected (no auth token) — middleware rejects fast without DB hit.

Peak Concurrency (GET /health)

Concurrency Requests Errors Duration Throughput Avg P95 Error%
10× 50 0 3 ms 19,190 req/s 0.4 ms 1.2 ms 0%
25× 100 0 5 ms 19,355 req/s 0.9 ms 1.9 ms 0%
50× 200 0 9 ms 23,318 req/s 1.4 ms 5.0 ms 0%
100× 500 0 31 ms 16,262 req/s 3.9 ms 6.6 ms 0%

Max throughput: ~23,318 req/s at 50 concurrent connections.
No errors at any load level. Latency grows linearly with concurrency.

Rate Limiting

  • 3-tier system: global (100/60s per IP), strict (10/60s), auth (5/60s)
  • Backend: Redis-backed sliding window
  • Verified: /api/auth/me returns 401 <0.5ms — rate limiting does not add material latency

Cache Circuit Breaker

Property Value
Failure threshold 3 consecutive failures
Half-open recovery window 15,000 ms (configurable)
Auto-recovery ✓ after window elapses
Request blocking during open circuit ✓ 5/5 blocked
Probe on half-open ✓ single request allowed through
Full reset on success

Production impact:

  • Without: Redis outage → every request times out → cascading failure → users see 500s
  • With: 3 failures → circuit opens → cache skipped for 15s → backend serves stale/DB data → 100% API availability maintained

Graceful Degradation

Mechanism Trigger Fallback Recovery Verified Lines
Cache circuit breaker 3× Redis failure DB fallback (null return) 15s half-open probe try/catch ✓ 140
Queue bypass Redis connection error No-op state (continue) Next op auto-retry try/catch ✓ 110
Email buffer Resend API non-2xx PendingEmail PostgreSQL insert Exp. backoff 5s→25s→125s (3 attempts) try/catch ✓ 508

Email retry backoff schedule:

  • Attempt 1: wait 5s
  • Attempt 2: wait 25s (cumulative 30s)
  • Attempt 3: wait 125s (cumulative 155s)

Without degradation:

  • Redis outage → 100% cache failure → 500 errors
  • Email outage → emails lost permanently
  • Queue outage → interview creation blocked

With degradation:

  • Cache → direct DB (100% uptime)
  • Email → DB buffer → retry (100% delivery rate)
  • Queue → bypass (100% uptime)

System Characteristics

Metric Value Source
Avg DB query time 1.70 ms EXPLAIN ANALYZE, 5 queries
Avg API response time 0.2 ms 60 sequential requests
Peak throughput 23,318 req/s 50 concurrent connections
P95 latency at 100× concurrency 6.6 ms 500 concurrent requests
Circuit breaker trip 3 failures Verified
Circuit breaker recovery 15 s (half-open window) Verified
Email delivery during outage 100% PendingEmail DB buffer
API availability during Redis outage 100% Graceful degradation path
Rate limiting tiers 3 (global/strict/auth) Redis-backed
Account lockout 5 failures / 15 min Redis-backed

Running Benchmarks Locally

# Prerequisites
#   - PostgreSQL on localhost:5432
#   - Backend on :3000 (bun run dev)

# Run all benchmarks at once
DATABASE_URL="postgresql://postgres:mysecretpassword@localhost:5432/postgres" \
  PORT=3000 \
  bash benchmarks/run-all.sh

# Or run individually
bun run benchmarks/01-db-queries.ts
bun run benchmarks/02-api-peak-load.ts
bun run benchmarks/03-circuit-breaker.ts
bun run benchmarks/04-graceful-degradation.ts

License

MIT

About

Evalio is a platform for realistic AI interview practice — helping you think out loud, communicate clearly, identify blind spots, and improve through adaptive interview simulations that get smarter over time.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages