
Bench
An electrical engineering interview prep drill with practice and timed interview modes, scoring breakdowns by topic and question type, and session persistence.
Timeline
Ongoing iteration
Role
Full Stack Developer
Team
Solo
Status
ActiveTechnology Stack
Key Challenges
- Designing a flexible scoring model that bridges casual practice and high-stakes interview settings
- Building session state and history persistence without a backend
- Crafting question breakdowns by topic and type without overwhelming the UI
- Keeping the question bank maintainable as types and difficulty dimensions expand
Key Learnings
- State serialization and resumption across page refreshes
- Weighted scoring across heterogeneous question modes
- Building a compact question format that scales to many dimensions (topic, type, difficulty)
- Graceful degradation when localStorage is unavailable
Bench
Overview
Bench is a drill app for electrical engineering interview prep. Build a session by selecting topics, question types, and session length, then either practice at your own pace or run a timed interview with a thinking-time clock. Every completed session logs to browser history, and you can resume an interrupted session exactly where you left off. Missed questions surface at the end for targeted review.
Why I built it
Interview prep lives in an awkward middle ground: you want fast feedback loops to practice weak concepts, but you also need to simulate the real thing—a timed slot where you're being graded. Most tools pick a side. Bench tries to do both, letting the same question bank and scoring model fit both modes without maintaining parallel feature sets.
Core features
Practice mode: unlimited time per question, self-score as right/wrong, instant feedback. The session summarizes accuracy by topic and question type.
Interview mode: a mono timer starts when each question appears and freezes when you reveal the answer—it measures thinking time, not time pressure. Self-scoring shifts to Strong / Partial / Missed (weighted 1 / 0.5 / 0), so interview sessions roll into the same percentile-based history as practice.
Session history: every completed session persists to localStorage (score, mode, topics attempted, missed question IDs). A progress panel on the setup screen shows session count and overall accuracy across the history.
In-progress resilience: the active session state is snapshotted to storage on every change, so a page refresh resumes exactly where you left off—same question queue, same scoring so far, same review state.
Filtering and breakdowns: setup screen filters by topic (EE domain) and question type (concept, debugging, design, calculation, trade-off, scenario, behavioral). Session summaries break score down both ways so you can spot weak spots at a glance.
Tech stack
Next.js 14 (App Router), React 18, TypeScript. Tailwind CSS and Framer Motion for the UI layer. No backend—all state is managed with useReducer + Context and persisted to localStorage with graceful degradation. Small footprint: just Next, React, Framer Motion, and a handful of utilities.
Technical highlights
Unified scoring across modes
A single gradeWeight() helper (strong / ok = 1, partial = 0.5, missed / bad = 0) lets practice and interview sessions score on the same scale. History rollups and breakdowns work the same regardless of mode, so a session is a session: the setup and scoring model don't bifurcate.
State shape that survives refreshes
Session state (question queue, current index, answers submitted, review state) lives in a reducer and is serialized to localStorage:bench:inprogress on every change. Hydration is eager and silent; if the stored state is corrupt or missing, the app boots into setup mode.
localStorage graceful degradation
Both history and in-progress session are tagged "best-effort." If localStorage is unavailable—private browsing, quota exhausted—the app silently switches to in-memory behavior. You still get a full session and can still take the next quiz; history just doesn't stick.
Multi-dimensional question filtering
Questions carry topic, type, and difficulty. The setup screen offers independent multi-select toggles for topics and types; the session-complete screen breaks score down by both dimensions. Adding a new question type or topic only requires adding it to lib/questions.ts and it flows through setup, filtering, and breakdowns automatically.
Challenges
Designing a scoring model that felt fair in both a low-stakes practice setting and a simulated interview was the first big hurdle—weighting partial credit (0.5) rather than binary pass/fail made interview mode feel more granular and less punishing, and unified the history meaningfully. Building session persistence without a backend meant careful serialization of the quiz state and resumption logic; the queue is immutable, the index tracks progress, and answers are a map, so hydration is deterministic.
What I learned
State machines and localStorage serialization are different problems from typical async/fetch patterns; there's no server to be the source of truth, so the client's stored shape has to be self-healing and unambiguous. Multi-select filters and dimensional breakdowns scale better with a well-defined question schema than with ad-hoc filtering. And graceful degradation (localStorage unavailable, corrupt history) is worth building in from day one—it's the difference between a degraded UX and a broken one.
Looking ahead
The next layer is moving the question bank out of lib/questions.ts into a structured file (JSON or YAML), making it easier to add bulk questions without shipping a new build. After that, optional backend sync so history follows you across devices. A Spaced Repetition mode is also a natural fit: surface previously-missed questions more often, without disrupting the core drill loop.
Final thoughts
Bench started as a way to nail the dual-mode problem: one app, two settings, one scoring model. It ended up being more about treating state and persistence as first-class concerns, not afterthoughts—localStorage as the source of truth, queries that don't require a backend, and a question format flexible enough to survive adding new dimensions without restructuring.