Day 4 of 100·Project Deep-Dive Day·653 words

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:

  1. 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."
  2. 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.js forwards 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.

01

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.

02

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.

03

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.