<?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: Squid Studio</title>
    <description>The latest articles on DEV Community by Squid Studio (@squid_studio_7f42dd4de082).</description>
    <link>https://dev.to/squid_studio_7f42dd4de082</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4027190%2F153b9494-171a-4875-98d2-57b65941fafe.png</url>
      <title>DEV Community: Squid Studio</title>
      <link>https://dev.to/squid_studio_7f42dd4de082</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/squid_studio_7f42dd4de082"/>
    <language>en</language>
    <item>
      <title>How I Built a Timed Quiz Platform with 400+ Questions</title>
      <dc:creator>Squid Studio</dc:creator>
      <pubDate>Mon, 13 Jul 2026 11:49:05 +0000</pubDate>
      <link>https://dev.to/squid_studio_7f42dd4de082/how-i-built-a-timed-quiz-platform-with-400-questions-84n</link>
      <guid>https://dev.to/squid_studio_7f42dd4de082/how-i-built-a-timed-quiz-platform-with-400-questions-84n</guid>
      <description>&lt;p&gt;I wanted to build a quiz app that felt fast no loading spinners, no account walls, no backend lag between questions. Just pick a category, pick a difficulty, and start answering. Here's how I approached building &lt;a href="https://www.quizoxa.com" rel="noopener noreferrer"&gt;QuizOxa&lt;/a&gt;, a free quiz platform with 8 categories and 3 difficulty levels, and a few of the technical decisions that shaped it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core challenge: making quizzes feel instant
&lt;/h2&gt;

&lt;p&gt;Most quiz apps I'd used before had a common problem every question click hit an API, added a delay, and broke the flow. I wanted question delivery to feel closer to a game than a form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preload the full question pool for a category client-side once, instead of fetching per-question&lt;/li&gt;
&lt;li&gt;Shuffle and slice the question set on the client using a simple Fisher-Yates shuffle, so every playthrough feels different&lt;/li&gt;
&lt;li&gt;Keep state (score, streak, timer) entirely in local component state no server round-trip needed until the quiz ends
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shuffleArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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;i&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;temp&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;arr&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 alone removed almost all perceived lag between questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timer logic: the trickiest part
&lt;/h2&gt;

&lt;p&gt;Each difficulty level needed a different countdown (30s / 20s / 15s) with points scaling accordingly (100 / 150 / 200 pts). The tricky part wasn't the timer itself it was making sure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The timer resets cleanly between questions without visual flicker&lt;/li&gt;
&lt;li&gt;A timeout is treated as a wrong answer, resets the streak, and still shows the explanation (so users learn even when they fail)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used a setInterval tied to component lifecycle with cleanup on unmount/question-change to avoid memory leaks and timer overlap bugs, a classic gotcha in countdown timers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explanations after every answer, right or wrong
&lt;/h2&gt;

&lt;p&gt;One decision that mattered a lot for retention: showing a short explanation after every answer, not just wrong ones. This turns the app from "test my knowledge" into "actually learn something," which matters if you want people to come back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing results without needing accounts
&lt;/h2&gt;

&lt;p&gt;No login system, instead, I generate a shareable result summary (score, accuracy, category) as plain text and hook it into the Web Share API for mobile, with a WhatsApp deep link fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;shareText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I scored &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; points on &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; quiz! Can you beat me? &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;quizUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;whatsappUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://wa.me/?text=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shareText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turned out to be a surprisingly effective, zero-cost growth loop, no ad spend, just organic shares.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I rebuilt this today, I'd add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local storage-based streak tracking across sessions (currently resets per session)&lt;/li&gt;
&lt;li&gt;A lightweight analytics layer to see which questions people get wrong most, to improve question quality over time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the shuffle/timer/scoring system in action, the live version is running at &lt;a href="https://www.quizoxa.com" rel="noopener noreferrer"&gt;QuizOxa&lt;/a&gt;, 8 categories, 400+ questions, no sign-up needed.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>react</category>
    </item>
  </channel>
</rss>
