<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ritik Jangir</title>
    <description>The latest articles on DEV Community by Ritik Jangir (@randomchikbum).</description>
    <link>https://dev.to/randomchikbum</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3590185%2F6702957b-8e5f-4f59-b902-f25500baeb28.png</url>
      <title>DEV Community: Ritik Jangir</title>
      <link>https://dev.to/randomchikbum</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/randomchikbum"/>
    <language>en</language>
    <item>
      <title>CEED Mock Test Platform - Situation Report</title>
      <dc:creator>Ritik Jangir</dc:creator>
      <pubDate>Mon, 12 Jan 2026 07:46:58 +0000</pubDate>
      <link>https://dev.to/randomchikbum/ceed-mock-test-platform-situation-report-2ja</link>
      <guid>https://dev.to/randomchikbum/ceed-mock-test-platform-situation-report-2ja</guid>
      <description>&lt;h1&gt;
  
  
  CEED Mock Test Platform - Situation Report
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; January 12, 2026&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Pre-Exam Infrastructure Crisis&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Next Review:&lt;/strong&gt; Post January 18, 2026&lt;/p&gt;




&lt;h2&gt;
  
  
  The Situation
&lt;/h2&gt;

&lt;p&gt;The app hit product-market fit unexpectedly. What started as a side project now has &lt;strong&gt;200 daily active users&lt;/strong&gt; consistently. The problem: I never charged for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current Infrastructure State
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Free Tier Limit&lt;/th&gt;
&lt;th&gt;Current Usage&lt;/th&gt;
&lt;th&gt;Overage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Egress&lt;/td&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;td&gt;5.10 GB&lt;/td&gt;
&lt;td&gt;0.10 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cached Egress&lt;/td&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;td&gt;8.79 GB&lt;/td&gt;
&lt;td&gt;3.79 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Overage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~4 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Usage Pattern
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;200 DAU&lt;/strong&gt; (Daily Active Users)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~25 MB egress per user per day&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Traffic peaked around Jan 4-6 (exam prep intensity)&lt;/li&gt;
&lt;li&gt;Grace period deadline: &lt;strong&gt;January 14, 2026&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;CEED Exam: &lt;strong&gt;January 18, 2026&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjth3vmi3g5yc0mbv2qtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjth3vmi3g5yc0mbv2qtf.png" alt="Egress Stats" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx0tnd89vx3wlu5jrda1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx0tnd89vx3wlu5jrda1.png" alt="Cached Egress Stats" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Happened
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No monetization&lt;/strong&gt; - I wanted to help students, didn't charge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organic growth&lt;/strong&gt; - Word of mouth brought 200 consistent users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High egress per user&lt;/strong&gt; - Questions, images, analytics, drill submissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing&lt;/strong&gt; - Peak usage right before the exam&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Immediate Actions Taken
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;✅ Decided to upgrade to Supabase Pro ($25/mo)&lt;/li&gt;
&lt;li&gt;✅ Emailed Supabase support requesting grace period extension&lt;/li&gt;
&lt;li&gt;✅ Documented situation for post-exam planning&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Post-Exam Plan (After January 18)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Week 1: Monetization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Add simple payment tier ($5/mo or ₹299/mo)&lt;/li&gt;
&lt;li&gt;[ ] Target 5% conversion = 10 paying users = $50/mo&lt;/li&gt;
&lt;li&gt;[ ] That covers Pro tier with buffer&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Week 2: Egress Optimization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Migrate images to Cloudflare R2 (zero egress fees)&lt;/li&gt;
&lt;li&gt;[ ] Add client-side caching for analytics data&lt;/li&gt;
&lt;li&gt;[ ] Implement CDN caching for static content&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Week 3: Scaling Prep
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Expecting 500-1000 DAU next session&lt;/li&gt;
&lt;li&gt;[ ] At 25MB/user/day, 1000 DAU = 25GB/month&lt;/li&gt;
&lt;li&gt;[ ] Pro tier includes 250GB - we're safe until 10K DAU&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Any system that consumes money must generate money or be rate-limited by design."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Charge from day one&lt;/strong&gt; - even a small amount validates demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor infrastructure costs weekly&lt;/strong&gt; - not monthly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build egress awareness into architecture&lt;/strong&gt; - cache aggressively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grace periods are finite&lt;/strong&gt; - don't assume they'll extend&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Written: January 12, 2026, 1:05 PM IST&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Exam in: 6 days&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>sass</category>
      <category>architecture</category>
      <category>learning</category>
    </item>
    <item>
      <title>Building Prepzilla: How I Made CEED Exam Practice Free for Everyone (And What I Learned)</title>
      <dc:creator>Ritik Jangir</dc:creator>
      <pubDate>Thu, 30 Oct 2025 21:02:56 +0000</pubDate>
      <link>https://dev.to/randomchikbum/building-prepzilla-how-i-made-ceed-exam-practice-free-for-everyone-and-what-i-learned-4n12</link>
      <guid>https://dev.to/randomchikbum/building-prepzilla-how-i-made-ceed-exam-practice-free-for-everyone-and-what-i-learned-4n12</guid>
      <description>&lt;h2&gt;
  
  
  Why Education Should Be Accessible
&lt;/h2&gt;

&lt;p&gt;When I started building &lt;a href="https://prepzilla.artelia.co.in" rel="noopener noreferrer"&gt;Prepzilla&lt;/a&gt;, my motivation was simple: &lt;strong&gt;education should be free, or at least accessible&lt;/strong&gt;. Not everyone can afford the hefty fees of coaching centers, yet those who do gain a significant advantage. This creates an unfair playing field for design entrance exam aspirants preparing for CEED (Common Entrance Exam for Design).&lt;/p&gt;

&lt;p&gt;I wanted to change that.&lt;/p&gt;

&lt;p&gt;What started as a weekend project sprouting from my own need to practice CEED questions quickly evolved into something much bigger. Within 24 hours of sharing it on Reddit (thanks to a shoutout from the r/designForge community), &lt;strong&gt;nearly 100 users&lt;/strong&gt; had signed up and started practicing. The response was overwhelming.&lt;/p&gt;

&lt;p&gt;But more importantly, it validated something I'd long suspected: &lt;strong&gt;there's a massive need for free, quality educational tools&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once I saw how quickly it resonated, I decided to document how it was built - both for transparency and for anyone curious about the technical process behind educational tools. Because this is also a first for me: &lt;strong&gt;I'm building &lt;em&gt;for&lt;/em&gt; users &lt;em&gt;with&lt;/em&gt; users&lt;/strong&gt;. Previously, I'd only built in isolation for clients and product managers. Now I have real people giving me transparent feedback, reporting bugs, suggesting features, and actively shaping the platform's future.&lt;/p&gt;

&lt;p&gt;Let's see how this shapes up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design Philosophy: Constraints as Features
&lt;/h2&gt;

&lt;p&gt;When building an educational tool, especially one aimed at exam preparation, the philosophy is different from building a typical web app. &lt;strong&gt;The interface needs to get out of the way&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Core Principles
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Question-first interface&lt;/strong&gt;: No distractions. The question is the hero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional over flashy&lt;/strong&gt;: Clean UI beats animations every time and the interface colors chosen were purposefully muted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed matters&lt;/strong&gt;: Fast load times mean more time for practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: Work on any device, any screen size, any internet speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Similarity&lt;/strong&gt;: Questions should look similar to the actual exam.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt;: Track performance and identify personalized patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: Allow users to practice in their own time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These weren't lofty ideals—they were born from constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget&lt;/strong&gt;: ₹0 for infrastructure (using Supabase free tier)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt;: Built in nights and weekends&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experience&lt;/strong&gt;: This was my learning ground for production-grade analytics (maybe)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? A minimalist, fast, distraction-free platform that does one thing well: &lt;strong&gt;help students practice CEED Part A questions effectively&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcur28ufowdd4tjz3t7pj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcur28ufowdd4tjz3t7pj.png" alt=" " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1151wlh2osjt2gv9b5ak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1151wlh2osjt2gv9b5ak.png" alt=" " width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fte8fxf15fn3qthawp9ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fte8fxf15fn3qthawp9ju.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Radix UI Over Others
&lt;/h3&gt;

&lt;p&gt;I'm not a huge fan of component libraries that lock you into specific design patterns, but I needed something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gave me &lt;strong&gt;unstyled, accessible primitives&lt;/strong&gt; (Radix UI)&lt;/li&gt;
&lt;li&gt;Let me work on business layer and service layer of the app and not be worried about UI&lt;/li&gt;
&lt;li&gt;Worked seamlessly with Next.js 16 and React 19&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I deliberately avoided Zustand and React Query in v1. Not because they're bad-they're excellent—but because I wanted to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only add complexity when &lt;em&gt;actually&lt;/em&gt; needed (not preemptively)&lt;/li&gt;
&lt;li&gt;I am not sure about the shape of the store yet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Turns out, I did need better data fetching (more on that later), but starting simple helped me understand &lt;em&gt;why&lt;/em&gt; I needed it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Breakdown: From Raw PDFs to Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Data Structuring: The PDF Hell
&lt;/h3&gt;

&lt;p&gt;This was by far the hardest part.&lt;/p&gt;

&lt;p&gt;CEED past papers are PDFs with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embedded images (diagrams, spatial reasoning questions)&lt;/li&gt;
&lt;li&gt;Text as images incorrectly rendered&lt;/li&gt;
&lt;li&gt;Complex layouts&lt;/li&gt;
&lt;li&gt;Inconsistent formatting across years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My initial approach&lt;/strong&gt;: Automate everything with a script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reality&lt;/strong&gt;: Partial success.&lt;/p&gt;

&lt;h4&gt;
  
  
  Attempt 1: Direct PDF Text Extraction
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// My naive first attempt&lt;/span&gt;
&lt;span class="c1"&gt;// Extract text + images → Structure questions → Upload to supabase → Done! &lt;/span&gt;
&lt;span class="c1"&gt;// (Spoiler: Didn't work)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Text came out almost fine but needed parsing to extract questions. Some text were rendered as images. No spatial information. Images were missing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Attempt 2: PDF.js for Embedded Images
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Try to extract embedded images&lt;/span&gt;
&lt;span class="c1"&gt;// Extract embedded images from PDF, rename them, and upload to Supabase storage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Only worked for PDFs that were correctly formatted with &lt;em&gt;embedded&lt;/em&gt; images. Many CEED papers have text rendered as images. It worked for some papers but not all. For one paper with 46 question images, the script extracted nearly 850 images. (DAYUM -_- they were all text rendered as images)&lt;/p&gt;

&lt;h4&gt;
  
  
  Attempt 3: OCR-Based Approach
&lt;/h4&gt;

&lt;p&gt;I turned to OCR tools (Tesseract.js, Google Cloud Vision API) thinking they'd solve everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: OCR introduced errors. Mathematical expressions became gibberish. Diagrams lost context.&lt;/p&gt;

&lt;h4&gt;
  
  
  Final Solution: Hybrid Manual + Smart Tooling
&lt;/h4&gt;

&lt;p&gt;I gave up on full automation and used a pragmatic approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Online PDF extraction tools&lt;/strong&gt; to separate text and image layers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual verification&lt;/strong&gt; of each question for accuracy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic categorization&lt;/strong&gt; of questions by topic based on keywords in the question text (took claude's help to build this)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured schema&lt;/strong&gt; to store everything consistently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Question uploader script&lt;/strong&gt; to label questions by topic
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// categorize.js - My manual categorization helper&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOPICS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Visualization and Spatial Reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Practical and Scientific Knowledge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Observation and Design Sensitivity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Environment and Society&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analytical and Logical Reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Language&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Creativity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Art and Design Knowledge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Design Methods and Practices&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// For each question, I manually assigned:&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;question_number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2016&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NAT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;question_type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NAT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;topic_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a1111111-1111-1111-1111-111111111111&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;question_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A cube of size 10 cm x 10 cm x 10 cm is taken and 2 cm x 2 cm square shaped tunnels are cut through the centres of opposite faces of the cube. Count the number of surfaces in the resulting object.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;correct_answer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;marks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;negative_marks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;difficulty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, this took &lt;strong&gt;hours&lt;/strong&gt;. But it resulted in &lt;strong&gt;clean, reliable data&lt;/strong&gt; that I could confidently build on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned&lt;/strong&gt;: Sometimes the "boring" manual work is the only way to ensure quality. Automation is great, but not at the cost of accuracy. And I need to upskill my automation script writing skills.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Database Schema: Designing for Analytics from Day One
&lt;/h3&gt;

&lt;p&gt;I knew analytics would be crucial, so I structured the database to support it from the start.&lt;/p&gt;

&lt;h4&gt;
  
  
  Core Tables
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Questions with rich metadata&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;topic_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;question_text&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;question_type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question_type&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MCQ'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MSQ'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'NAT'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NAT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MSQ'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MCQ'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;correct_answer&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;marks&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;negative_marks&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;year&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;difficulty&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;difficulty&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Easy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Medium'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hard'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Test attempts with metadata&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;user_profiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;total_questions&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;correct_answers&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;duration_seconds&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Per-question analytics&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;attempt_answers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;attempt_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;question_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;user_answer&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;is_correct&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;marks_obtained&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;time_spent_seconds&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- ⚠️ This was crucial&lt;/span&gt;
  &lt;span class="n"&gt;selected_options&lt;/span&gt; &lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- For MSQ tracking&lt;/span&gt;
  &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;: The &lt;code&gt;time_spent_seconds&lt;/code&gt; and &lt;code&gt;selected_options&lt;/code&gt; fields were afterthoughts that became &lt;strong&gt;game-changers&lt;/strong&gt; for analytics.&lt;/p&gt;

&lt;p&gt;They enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time allocation analysis&lt;/strong&gt; (which sections take too long?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSQ strategy tracking&lt;/strong&gt; (how many options do users typically select?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rushed vs. stuck questions&lt;/strong&gt; (identifying patterns)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. The Infamous N+1 Problem
&lt;/h3&gt;

&lt;p&gt;Early on, I made a classic mistake that haunts many developers: &lt;strong&gt;the N+1 query problem&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Problem
&lt;/h4&gt;

&lt;p&gt;When loading a test with 44 questions (CEED's standard format), my code was doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ BAD: N+1 queries&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;questions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchQuestions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Fetching options for EACH question = N queries&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;question_options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;question_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: 44+ database queries just to load one test. Terrible performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Solution
&lt;/h4&gt;

&lt;p&gt;Batch fetch everything in &lt;strong&gt;one query&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ GOOD: Single query for all options&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;questionIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;allOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;question_options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;question_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;questionIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ascending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Group by question_id client-side&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optionsMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;question_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;question_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;question_id&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact&lt;/strong&gt;: Reduced test loading time from &lt;strong&gt;~9 seconds to ~300ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: Always think about database query patterns. Modern ORMs make it easy to accidentally create N+1 problems.&lt;/p&gt;

&lt;p&gt;Before you google what N+1 problem is&lt;/p&gt;

&lt;p&gt;Analogy&lt;br&gt;
Ordering pizza:&lt;/p&gt;

&lt;p&gt;N+1 version = you call the restaurant once per topping.&lt;/p&gt;

&lt;p&gt;Optimized version = you tell them all toppings in one order.&lt;br&gt;
Both achieve the same thing, but the second avoids wasted trips.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. Architecture: Component Structure
&lt;/h3&gt;

&lt;p&gt;I structured the app around &lt;strong&gt;separation of concerns&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
├── test/
│   ├── create/         # Test configuration
│   ├── session/        # Active test interface
│   └── results/        # Post-test analytics
├── dashboard/          # User overview
├── admin/              # Question management
└── auth/               # Authentication flows

components/
├── test/
│   ├── question-content.tsx   # Question display
│   ├── navigation-buttons.tsx # Prev/Next/Submit
│   └── question-palette.tsx   # Visual navigation
└── analytics/
    ├── section-performance.tsx
    ├── msq-strategy.tsx
    └── marks-lost-breakdown.tsx

hooks/
├── use-test-session.ts      # State management
├── use-test-navigation.ts   # Question switching
├── use-test-submission.ts   # Scoring &amp;amp; saving
└── useAnalytics.ts          # Analytics calculations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Hooks for Clean Separation
&lt;/h3&gt;

&lt;p&gt;Instead of a massive component with 500+ lines, I split logic into focused hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hooks/use-test-navigation.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useTestNavigation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setAnswers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;visitedQuestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setVisitedQuestions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;questionTimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuestionTimes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleAnswerChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;questionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;questionId&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleQuestionChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Track time spent on current question&lt;/span&gt;
    &lt;span class="nf"&gt;trackTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setVisitedQuestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newIndex&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;visitedQuestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;questionTimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;handleAnswerChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;handleQuestionChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made the main test component &lt;strong&gt;clean and readable&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TestSessionPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTestSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleAnswerChange&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTestNavigation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handleSubmitTest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTestSubmission&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TestPageSkeleton&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;QuestionContent&lt;/span&gt; 
        &lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="nx"&gt;onAnswerChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleAnswerChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavigationButtons&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmitTest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. Analytics Layer: Making Data Actionable
&lt;/h3&gt;

&lt;p&gt;Raw numbers are boring. &lt;strong&gt;Insights are valuable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I built an analytics system that doesn't just show "You got 65%" but tells you &lt;strong&gt;why&lt;/strong&gt; and &lt;strong&gt;how to improve&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Section Performance Breakdown
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hooks/useAnalytics.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processSectionPerformance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;questionInfoMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NAT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MSQ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MCQ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sectionAnswers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
      &lt;span class="nx"&gt;questionInfoMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;question_id&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;correct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sectionAnswers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_correct&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attempted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sectionAnswers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_answer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;avgTimeSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sectionAnswers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time_spent_seconds&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;attempted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;attempted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;correct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;correct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;attempted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;avgTimeSeconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;marksObtained&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sectionAnswers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marks_obtained&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MSQ Strategy Analysis
&lt;/h4&gt;

&lt;p&gt;One of my favorite features: &lt;strong&gt;tracking how many options users select in MSQ questions&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processMSQStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;questionInfoMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msqAnswers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;questionInfoMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;question_id&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MSQ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;strategies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;oneOption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;totalMarks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;twoOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;totalMarks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;threeOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;totalMarks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;fourOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;totalMarks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;msqAnswers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optionCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_answer&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Categorize by strategy&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;optionCount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oneOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... similar for 2, 3, 4 options&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Generate recommendation&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Keep practicing MSQ questions.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twoOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oneOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Great! Your 2-option strategy is effective.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells users: "Hey, you're selecting 4 options too often and losing marks. Try being more conservative."&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Tech Stack Decisions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What I Used
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt;: For SSR, routing, and deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt;: Type safety across the board&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt;: PostgreSQL database + auth + RLS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt;: Utility-first styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radix UI&lt;/strong&gt;: Headless accessible components&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What I Deliberately Avoided (For Now)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zustand&lt;/strong&gt;: React's &lt;code&gt;useState&lt;/code&gt; + &lt;code&gt;useContext&lt;/code&gt; was enough for v1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Query&lt;/strong&gt;: Built custom hooks first; will add when caching becomes painful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prisma&lt;/strong&gt;: Supabase client was sufficient&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Starting simple let me ship fast. I'll add complexity only when the pain points justify it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Database Schema Visualization
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9s8jkgu90481b5pidw3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9s8jkgu90481b5pidw3.png" alt="database schema" width="800" height="633"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐       ┌─────────────────┐       ┌──────────────────┐
│   topics    │◄──────┤    questions    │◄──────┤ question_options │
└─────────────┘       └─────────────────┘       └──────────────────┘
                             │ ▲                         │
                             │ │                         │
                             │ │                         │
                             ▼ │                         ▼
                      ┌───────────────┐            (MCQ/MSQ only)
                      │question_topics│
                      └───────────────┘
                             │
                             │
                             ▼
┌──────────────┐      ┌─────────────┐      ┌──────────────────┐
│user_profiles │◄─────┤  attempts   │◄─────┤ attempt_answers  │
└──────────────┘      └─────────────┘      └──────────────────┘
      │                     │                      │
      │                     │                      ├─► time_spent_seconds
      │                     │                      ├─► marks_obtained
      │                     │                      ├─► selected_options
      │                     │                      └─► skipped
      │                     │
      │                     ▼
      │              ┌─────────────────┐
      │              │ attempt_topics  │
      │              └─────────────────┘
      │
      │
      │        ┌──────────────────┐
      ├──────► │ question_reports │ (User feedback on questions)
      │        └──────────────────┘
      │
      └──────► ┌──────────────────┐
               │general_feedback  │ (Bug reports, feature requests)
               └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reflection: Building &lt;em&gt;With&lt;/em&gt; Users, Not Just &lt;em&gt;For&lt;/em&gt; Them
&lt;/h2&gt;

&lt;p&gt;This project is a &lt;strong&gt;first for me&lt;/strong&gt; in a crucial way.&lt;/p&gt;

&lt;p&gt;Previously, I'd built tools in isolation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Come up with an idea&lt;/li&gt;
&lt;li&gt;Build it alone&lt;/li&gt;
&lt;li&gt;Release it&lt;/li&gt;
&lt;li&gt;Hope people use it&lt;/li&gt;
&lt;li&gt;(Usually crickets)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;This time was different.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Within 24 hours of sharing on Reddit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~100 users&lt;/strong&gt; signed up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Couple of bug reports&lt;/strong&gt; came in (caught edge cases I missed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature requests&lt;/strong&gt; started flooding in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent feedback&lt;/strong&gt; from real CEED aspirants&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Feedback Loop
&lt;/h3&gt;

&lt;p&gt;One user reported: &lt;em&gt;"The timer doesn't pause when I switch tabs!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I initially thought: "That's intentional - it's a real exam simulation."&lt;/p&gt;

&lt;p&gt;But then &lt;strong&gt;multiple users&lt;/strong&gt; said the same thing. Turns out, many students practice on mobile and frequently get interruptions (calls, messages). Making the timer strict was hurting the experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;: Added a warning when switching tabs, but didn't pause (compromise).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Another user&lt;/strong&gt;: &lt;em&gt;"Can we have a practice mode without the timer?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's now at the mid of my roadmap.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'm Learning
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Building with users means:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rapid iteration&lt;/strong&gt; based on real pain points&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritization&lt;/strong&gt; becomes clearer (what users actually want vs. what I &lt;em&gt;think&lt;/em&gt; they want)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motivation&lt;/strong&gt; stays high (people are using this!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accountability&lt;/strong&gt; (bugs affect real people, not just me)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's exciting but also &lt;strong&gt;humbling&lt;/strong&gt;. I'm learning that my assumptions about what users need are often wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges &amp;amp; Lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The journey of PDF Data Extraction to Structured Data is Brutal
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: Don't underestimate the difficulty of working with unstructured data. Automation sounds nice, but quality &amp;gt; speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Performance Matters More Than Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: A fast, simple app beats a slow, feature-rich one every time. Optimize early (but not prematurely).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Analytics Need Context
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: Raw numbers don't help. "You scored 65%" is useless. "You're rushing MCQs (avg 45s) but stuck on NAT (avg 150s). Try allocating time better." is actionable.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Users Find Bugs You Never Imagined
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: Real users stress-test your app in ways you never will. Embrace feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Constraints Force Creativity
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: Having zero budget forced me to use free tiers creatively. Supabase's free tier is surprisingly generous for small-scale apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Outcomes &amp;amp; Next Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current Stats (Day 3)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~100 users&lt;/strong&gt; signed up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;600+ questions&lt;/strong&gt; added&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10 tests&lt;/strong&gt; added&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active feedback loop&lt;/strong&gt; (through DesignForge WhatsApp and in-app feedback tables)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;₹0 cost&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wins
&lt;/h3&gt;

&lt;p&gt;✅ Fast UI, accurate scoring, deep analytics, mobile-ready, free. &lt;/p&gt;

&lt;h3&gt;
  
  
  What Needs Work
&lt;/h3&gt;

&lt;p&gt;⚠️ Practice mode (without timer)&lt;br&gt;&lt;br&gt;
⚠️ Question bookmarking&lt;br&gt;
⚠️ More detailed performance history&lt;br&gt;
⚠️ Performance history per attempt&lt;br&gt;
⚠️ Retry attempts feature&lt;br&gt;
⚠️ Community features (discussion forums?)&lt;br&gt;
⚠️ ANSWER EXPLANATIONS&lt;/p&gt;

&lt;h3&gt;
  
  
  Roadmap
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Phase 1: Core Improvements (Next Week)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Add practice mode (untimed, instant feedback)&lt;/li&gt;
&lt;li&gt;[ ] Implement question bookmarking&lt;/li&gt;
&lt;li&gt;[ ] Add explanation to answers (Still wondering how to do it, if any ideas drop them in the comments)&lt;/li&gt;
&lt;li&gt;[ ] Optimize for React Query (caching)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 2: Community Features (Next Month)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Add discussion forums per question&lt;/li&gt;
&lt;li&gt;[ ] Peer comparison (anonymized)&lt;/li&gt;
&lt;li&gt;[ ] Study groups / challenges&lt;/li&gt;
&lt;li&gt;[ ] Weekly leaderboards&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 3: Expansion (Month 3+)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Add UCEED (Undergraduate CEED)&lt;/li&gt;
&lt;li&gt;[ ] Add NID entrance prep&lt;/li&gt;
&lt;li&gt;[ ] Gamification (streaks, badges)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  For Other Builders: Key Takeaways
&lt;/h2&gt;

&lt;p&gt;If you're building an educational tool (or any product), here's what I'd recommend:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with the Problem, Not the Tech
&lt;/h3&gt;

&lt;p&gt;I didn't set out to "build with React 19" or "try Supabase." I wanted to solve a specific problem: &lt;strong&gt;make CEED practice accessible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The tech stack followed from that goal.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ship Fast, Iterate Faster
&lt;/h3&gt;

&lt;p&gt;My first version was rough (I've tried building such a platform before). Timer bugs, UI glitches, missing features. But i &lt;strong&gt;survived and grew&lt;/strong&gt;, and that's what mattered.&lt;/p&gt;

&lt;p&gt;Shipping fast meant getting feedback fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Analytics Are Your Product's Teacher
&lt;/h3&gt;

&lt;p&gt;Don't just track "users signed up." Track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where do users drop off?&lt;/li&gt;
&lt;li&gt;Which features are actually used?&lt;/li&gt;
&lt;li&gt;What errors occur most often?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This data tells you what to build next.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Manual Work Isn't Failure
&lt;/h3&gt;

&lt;p&gt;I spent hours manually structuring question data. It felt like a waste of time.&lt;/p&gt;

&lt;p&gt;But that manual work created a &lt;strong&gt;high-quality dataset&lt;/strong&gt; that automation would've ruined.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Users Will Surprise You
&lt;/h3&gt;

&lt;p&gt;You think you know what they need. You don't.&lt;/p&gt;

&lt;p&gt;Build, ship, listen, adapt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Education as a Right, Not a Privilege
&lt;/h2&gt;

&lt;p&gt;Building &lt;strong&gt;Prepzilla&lt;/strong&gt; has been fun, not because of the tech challenges, but because it's making a real difference.&lt;/p&gt;

&lt;p&gt;Nearly 100 students used it in the first 24 hours. That's 100 people who now have access to quality CEED practice &lt;strong&gt;for free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And this is just the beginning.&lt;/p&gt;

&lt;p&gt;If you're preparing for CEED, give it a try: &lt;strong&gt;&lt;a href="https://prepzilla.artelia.co.in" rel="noopener noreferrer"&gt;prepzilla.artelia.co.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're a builder, I hope this article inspires you to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build something that matters&lt;/li&gt;
&lt;li&gt;Ship before it's perfect&lt;/li&gt;
&lt;li&gt;Listen to your users&lt;/li&gt;
&lt;li&gt;Iterate relentlessly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And if you believe education should be accessible, &lt;strong&gt;help spread the word&lt;/strong&gt;. Share this tool with anyone preparing for design entrance exams.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connect &amp;amp; Contribute
&lt;/h2&gt;

&lt;p&gt;If you're a CEED aspirant, developer, or just someone who believes in accessible education, I'd love to hear from you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://prepzilla.artelia.co.in" rel="noopener noreferrer"&gt;prepzilla.artelia.co.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback&lt;/strong&gt;: Built-in feedback form (or send me a mail: &lt;a href="mailto:jangir.ritik06@gmail.com"&gt;jangir.ritik06@gmail.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature Requests&lt;/strong&gt;: Always open to suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Special thanks to the &lt;a href="https://reddit.com/r/designForge" rel="noopener noreferrer"&gt;&lt;strong&gt;r/designForge&lt;/strong&gt;&lt;/a&gt; community for the initial shoutout and support. You gave this project wings.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building for users, with users. Let's make learning accessible.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: #Education #WebDev #React #Next.js #OpenSource #CEED #DesignEducation #EdTech #BuildInPublic&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>buildinpublic</category>
      <category>tutorial</category>
      <category>development</category>
    </item>
  </channel>
</rss>
