What is Traverse? Passive capture, spaced repetition, and the resume you must defend
The domain tour of Traverse — and the BullMQ-vs-RabbitMQ discrepancy you need to fix in your head.
Know your own system
Traverse is a DSA-learning companion: it passively captures every LeetCode/CodeChef submission you make, then schedules spaced-repetition revisions of the problems you solved, tracks streaks and rings (Apple-Fitness-style), and shows analytics. A Chrome extension captures activity on problem platforms; a native iOS app (SwiftUI) consumes the API; an Express backend does the rest.
The architecture in one diagram
Chrome extension (leetFeedback-extension) │ MAIN-world net interceptor page/net-interceptor.js │ platforms/<name>.js adapters parse judge responses ▼ POST /api/submissions ──► Express (src/index.ts) │ auth (JWT cookie or Bearer) idempotencyKey, deviceId ▼ SubmissionService.createSubmission ├─ verify device (not revoked) ├─ duplicate check: @@unique([userId, idempotencyKey]) ├─ find-or-create Problem (platform, slug) ├─ insert Submission row ← DB write, synchronous ├─ Solve sync + XP (easy 10/med 20/hard 30) immediate └─ enqueue: accepted+AI ──► ai-analysis-processing (BullMQ, concurrency 5) otherwise ──► submission-processing (BullMQ, concurrency 10) │ AI worker: LLM ladder → tags/tier/score ── forwards ──┐ Submission worker: FSRS revision scheduling, │ streaks, rings, achievements, cache invalidation ◄──┘ │ Redis (cache + BullMQ) PostgreSQL (Prisma, docker postgres:17)
Three queues (src/queues/): submission-processing, ai-analysis-processing, notification-delivery. Notifications use a Postgres outbox (Notification + NotificationDelivery) drained every 30 s — an elegant pattern you'll meet again in zoth's outbox_events.
The one thing you must get straight: RabbitMQ vs BullMQ
Your resume says "RabbitMQ-based asynchronous ingestion pipeline". The codebase says BullMQ on Redis — no AMQP anywhere (verified: no amqp dependency, queues are BullMQ Queue/Worker objects in src/queues/*.ts, Redis clients in src/config/redis.ts). Options, pick one and rehearse it:
- Best option — update the resume to "Redis-backed BullMQ job queue" (still a 50x story, equally impressive, and now bulletproof). Then in interviews you can shine: "I evaluated RabbitMQ-style brokers, but for a single-node deployment where durability comes from the DB write happening synchronously and jobs are small, a Redis-backed queue removed a whole broker from the ops budget."
- If the resume has already gone out: own it proactively — "the resume summarizes it as a message queue; the implementation is BullMQ over Redis; RabbitMQ was the pattern I was applying." Never let an interviewer discover it.
Either way, your zoth RabbitMQ knowledge is real and deep (Day 60), so you're covered on both technologies.
Resume bullets decoded
- "Scaled passive DSA activity capture to 100+ users… TypeScript backend powering a native iOS client and Chrome Extension" — capture happens in the extension via network interception, not scraping:
page/net-interceptor.jsforwards judge API traffic to platform adapters which POST structured verdicts. Be ready to explain the difference (interception = parse the platform's own API responses in-page; scraping = parse DOM; scraping is brittle, interception is precise). - "Cut submission latency 50x — 10s to 0.2s" — the 10 s came from a sequential LLM provider ladder with 15 s per-provider timeouts on the request thread; the fix moved it to
ai-analysis-processing. The 0.2 s is DB write + enqueue. The AI worker always forwards to the submission queue regardless of AI outcome, so scheduling never blocks on AI. - Popin Tech bullets are separate — Day 98.
Domain vocabulary you'll be asked about
Spaced repetition / FSRS: the scheduler is a hand-rolled FSRS-5 implementation (Day 52–54) — power-law forgetting curve R(t,S) = (1 + 19/81 · t/S)^(-0.5), per-problem difficulty & stability, target retrievability 0.85 (not 0.9 — a marketing-vs-math distinction you should be precise about).
IdempotencyKey: every submission from the extension carries one; @@unique([userId, idempotencyKey]) makes double-POSTs (extension retries) safe.
BYOK AI: users can supply their own Gemini key per request; server falls back to Groq, then G4F. Premium gating: free users get cognitiveTier/recallScore nulled at one choke point (aiAnalysis.queue.ts:143), so scheduling is fully deterministic for them.
Bulletproof your resume
Say these answers aloud before moving on
These are the follow-ups an interviewer will actually ask about your projects.
Walk me through what happens when I submit a problem on LeetCode with the extension installed.
The extension's MAIN-world interceptor sees the judge's API response, the platform adapter converts it into a verdict (slug, platform, outcome, attempts with code snapshots, idempotencyKey), and POSTs /api/submissions with a JWT. The server verifies auth + device, dedupes by (userId, idempotencyKey), finds-or-creates the Problem (topic set-once), inserts the Submission, awards XP if it's a first solve, then enqueues: AI analysis runs on a BullMQ worker (LLM ladder with fallbacks), and the submission worker initializes/updates the FSRS revision schedule, upserts StreakDay, syncs rings, checks achievements, invalidates caches. The user's request returned 202 within ~200 ms of all that starting.
Why a queue instead of just doing everything after the response?
Three reasons: (1) latency — the AI ladder can take up to 15 s per provider × several providers; (2) isolation — an LLM outage would otherwise fail submissions; (3) concurrency control — BullMQ workers run at concurrency 10 (submissions) and 5 (AI) with per-second limiters, so a burst of solves processes in order without hammering providers. Also retries: failed jobs retry with exponential backoff (3 attempts, 2 s base) instead of being lost.
What's the difference between your queue and Kafka?
BullMQ is a work queue: jobs are consumed and removed, at-least-once, no replay. Kafka is a distributed log: ordered, partitioned, replayable, built for high throughput and multiple independent consumers. For traverse, there's no need for replay or multiple consumer groups — the source of truth is Postgres, and jobs are point-in-time triggers. Zoth's RabbitMQ is the same argument against Kafka: 5 routing keys, one consumer queue, $30 budget.