Learn
🤖 AI Hub 📰 News 📖 Dictionary 🧰 Resources
Grow
💼 Jobs 🚀 Community 🏆 Challenges
Explore by Stage
🌑 Zero 📚 Learn 🔧 Practice 🏗 Build 🚀 Launch 💰 Earn
Account
Login Sign Up
🏗️HTML 🎨CSS ⚡JavaScript 🐙Git & GitHub ⚛️React Soon 📘TypeScript Soon 🐍Python 🗄️Database & SQL Soon 🌐Web Development Soon ☁️Cloud & DevOps Soon 🤖AI & ML Soon 📄Resume Prep Soon 🎤Interview Prep Soon 💼Freelancing Soon 🚀SaaS & Side Projects Soon 🤖Automation Soon
Advanced 🕑 36 min read 👁 1 views 📅 Oct 21, 2026

Project: Build a Quiz App with Score and Timer

Build a general knowledge quiz app step by step: one question at a time, right and wrong highlights, a score, a 15-second timer per question and a result screen with Play again.

The Story: Rahul’s Daily GK Practice

Rahul is preparing for a government job exam. Every exam has a general knowledge section, and the questions come fast: you read, choose and move on, with a clock running. Rahul wants a small practice tool on his phone that asks him questions one by one, shows if he was right, gives him only 15 seconds for each, and tells him his score at the end.

In this project you will build that quiz app with plain JavaScript. It is a great project because it has state (which question we are on, the score, the seconds left) and screens (the quiz screen and the result screen). Learning to manage these two ideas is the heart of building any interactive app.

How to follow this project: each step below is a complete page that you can run with ▶ Try it yourself. Each step adds one feature to the previous one, so compare the code to see what is new.

The Plan

StepFeatureMain skill
1Layout and question dataArray of objects
2Show one questionforEach, createElement
3Click an option, see right or wrongEvent delegation, classes
4Next button and progressMoving through state
5ScoreCounters
615-second timersetInterval, clearInterval
7Result screen and Play againPercentages, resetting state

Step 1: The Layout and the Questions

First, the look of the quiz, written by hand: a progress label, a score, the question and four option buttons. Under it, in JavaScript, we write our five questions as an array of objects.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>
    <div class="top">
      <span id="progress">Question 1 of 5</span>
      <span id="score">Score: 0</span>
    </div>
    <p class="question" id="question">What is the capital of India?</p>
    <div class="options" id="options">
      <button type="button" class="option">Mumbai</button>
      <button type="button" class="option">Kolkata</button>
      <button type="button" class="option">New Delhi</button>
      <button type="button" class="option">Chennai</button>
    </div>
    <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    console.log(`Loaded ${questions.length} questions`); // Loaded 5 questions
    console.log(questions[0].options[questions[0].answer]); // New Delhi
  </script>
</body>
</html>

Each question object has three properties:

  • question: the text to show.
  • options: an array of four answers.
  • answer: the index (position) of the right option. Arrays count from 0, so answer: 2 means the third option, “New Delhi”.

The second console.log proves the data is right: questions[0].options[questions[0].answer] reads the first question’s correct option. Keeping questions as data, separate from the HTML, means Rahul can add a hundred questions later without touching the page.

Step 2: Show One Question from the Array

Now the HTML has empty containers, and JavaScript fills them. The variable current remembers which question we are on. It starts at 0.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>
    <div class="top">
      <span id="progress"></span>
      <span id="score">Score: 0</span>
    </div>
    <p class="question" id="question"></p>
    <div class="options" id="options"></div>
    <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');

    let current = 0;

    function showQuestion() {
      const q = questions[current];
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
    }

    showQuestion();
  </script>
</body>
</html>
  • const q = questions[current] picks the current question object.
  • optionsEl.innerHTML = '' removes old buttons before drawing new ones.
  • forEach((option, index) => ...) gives us each option and its position.
  • btn.dataset.index = index stores the position on the button as a data-index attribute, so we know later which option was clicked.

Change let current = 0 to 3 and run it: the planet question appears. The page is now driven by data.

Step 3: Click an Option and Highlight Right or Wrong

When Rahul taps an option, the right answer should turn green. If he chose wrongly, his choice should turn red. After that, no more clicks should count.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>
    <div class="top">
      <span id="progress"></span>
      <span id="score">Score: 0</span>
    </div>
    <p class="question" id="question"></p>
    <div class="options" id="options"></div>
    <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');

    let current = 0;

    function showQuestion() {
      const q = questions[current];
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
    }

    function revealAnswer(chosenIndex) {
      const correctIndex = questions[current].answer;
      for (const btn of optionsEl.children) {
        const index = Number(btn.dataset.index);
        btn.disabled = true;
        if (index === correctIndex) {
          btn.classList.add('correct');
        } else if (index === chosenIndex) {
          btn.classList.add('wrong');
        }
      }
    }

    optionsEl.addEventListener('click', (event) => {
      const btn = event.target.closest('.option');
      if (!btn || btn.disabled) return;
      revealAnswer(Number(btn.dataset.index));
    });

    showQuestion();
  </script>
</body>
</html>
  • We use event delegation: one listener on the options container, not one per button. The buttons are rebuilt for every question, but the container stays. closest('.option') finds the button that was clicked.
  • revealAnswer(chosenIndex) loops over every button with for...of on optionsEl.children.
  • Number(btn.dataset.index) turns the attribute back into a number, because attributes are always strings.
  • Every button gets disabled = true, so the answer is locked.
  • The correct button gets the correct class. The chosen button, if it is a different one, gets wrong. The CSS colours them.

Showing the right answer even when Rahul is wrong is good teaching: he learns something from every mistake.

Step 4: The Next Button and Progress

Now we can move through all five questions. The Next button stays disabled until Rahul answers, so he cannot skip a question by accident.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>
    <div class="top">
      <span id="progress"></span>
      <span id="score">Score: 0</span>
    </div>
    <p class="question" id="question"></p>
    <div class="options" id="options"></div>
    <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');
    const progressEl = document.querySelector('#progress');
    const nextBtn = document.querySelector('#next-btn');

    let current = 0;

    function showQuestion() {
      const q = questions[current];
      progressEl.textContent = `Question ${current + 1} of ${questions.length}`;
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
      nextBtn.disabled = true;
      nextBtn.textContent = current === questions.length - 1 ? 'See result' : 'Next';
    }

    function revealAnswer(chosenIndex) {
      const correctIndex = questions[current].answer;
      for (const btn of optionsEl.children) {
        const index = Number(btn.dataset.index);
        btn.disabled = true;
        if (index === correctIndex) {
          btn.classList.add('correct');
        } else if (index === chosenIndex) {
          btn.classList.add('wrong');
        }
      }
      nextBtn.disabled = false;
    }

    optionsEl.addEventListener('click', (event) => {
      const btn = event.target.closest('.option');
      if (!btn || btn.disabled) return;
      revealAnswer(Number(btn.dataset.index));
    });

    nextBtn.addEventListener('click', () => {
      current++;
      if (current < questions.length) {
        showQuestion();
      } else {
        progressEl.textContent = 'Finished';
        questionEl.textContent = 'Quiz finished! Well done.';
        optionsEl.innerHTML = '';
        nextBtn.disabled = true;
      }
    });

    showQuestion();
  </script>
</body>
</html>
  • In showQuestion(), a template string writes “Question 2 of 5”. We add 1 because current starts at 0 but people count from 1.
  • On the last question, the button text changes to “See result”. The ternary operator condition ? a : b chooses between the two words.
  • revealAnswer now ends with nextBtn.disabled = false.
  • The Next handler adds 1 to current. If there are questions left, we show the next one. If not, we show a simple “finished” message for now.

Step 5: Keep the Score

A new variable, score, starts at 0. Inside revealAnswer, if the chosen index equals the correct index, we add 1 and update the score label.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>
    <div class="top">
      <span id="progress"></span>
      <span id="score">Score: 0</span>
    </div>
    <p class="question" id="question"></p>
    <div class="options" id="options"></div>
    <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');
    const progressEl = document.querySelector('#progress');
    const nextBtn = document.querySelector('#next-btn');
    const scoreEl = document.querySelector('#score');

    let current = 0;
    let score = 0;

    function showQuestion() {
      const q = questions[current];
      progressEl.textContent = `Question ${current + 1} of ${questions.length}`;
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
      nextBtn.disabled = true;
      nextBtn.textContent = current === questions.length - 1 ? 'See result' : 'Next';
    }

    function revealAnswer(chosenIndex) {
      const correctIndex = questions[current].answer;
      for (const btn of optionsEl.children) {
        const index = Number(btn.dataset.index);
        btn.disabled = true;
        if (index === correctIndex) {
          btn.classList.add('correct');
        } else if (index === chosenIndex) {
          btn.classList.add('wrong');
        }
      }
      if (chosenIndex === correctIndex) {
        score++;
        scoreEl.textContent = `Score: ${score}`;
      }
      nextBtn.disabled = false;
    }

    optionsEl.addEventListener('click', (event) => {
      const btn = event.target.closest('.option');
      if (!btn || btn.disabled) return;
      revealAnswer(Number(btn.dataset.index));
    });

    nextBtn.addEventListener('click', () => {
      current++;
      if (current < questions.length) {
        showQuestion();
      } else {
        progressEl.textContent = 'Finished';
        questionEl.textContent = `Quiz finished! You scored ${score} out of ${questions.length}.`;
        optionsEl.innerHTML = '';
        nextBtn.disabled = true;
      }
    });

    showQuestion();
  </script>
</body>
</html>

Notice that the score logic lives in exactly one place. Later, when the timer runs out, it will call the same revealAnswer function with null. Since null never equals a real index, a timeout simply scores nothing, with no extra code.

Step 6: A 15-Second Timer for Each Question

Real exams are timed, so Rahul wants pressure. setInterval(fn, 1000) runs a function every 1000 milliseconds (one second) and returns an id. clearInterval(id) stops it. The JavaScript Event Loop lesson explains how timers work behind the scenes; here we use them for real.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>
    <div class="top">
      <span id="progress"></span>
      <span id="timer">Time: 15s</span>
      <span id="score">Score: 0</span>
    </div>
    <p class="question" id="question"></p>
    <div class="options" id="options"></div>
    <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');
    const progressEl = document.querySelector('#progress');
    const nextBtn = document.querySelector('#next-btn');
    const scoreEl = document.querySelector('#score');
    const timerEl = document.querySelector('#timer');

    const TIME_LIMIT = 15;
    let current = 0;
    let score = 0;
    let timeLeft = TIME_LIMIT;
    let timerId = null;

    function showQuestion() {
      const q = questions[current];
      progressEl.textContent = `Question ${current + 1} of ${questions.length}`;
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
      nextBtn.disabled = true;
      nextBtn.textContent = current === questions.length - 1 ? 'See result' : 'Next';
      startTimer();
    }

    function startTimer() {
      clearInterval(timerId);
      timeLeft = TIME_LIMIT;
      timerEl.textContent = `Time: ${timeLeft}s`;

      timerId = setInterval(() => {
        timeLeft--;
        timerEl.textContent = `Time: ${timeLeft}s`;
        if (timeLeft === 0) {
          timerEl.textContent = "Time's up!";
          revealAnswer(null);
        }
      }, 1000);
    }

    function revealAnswer(chosenIndex) {
      clearInterval(timerId);
      const correctIndex = questions[current].answer;
      for (const btn of optionsEl.children) {
        const index = Number(btn.dataset.index);
        btn.disabled = true;
        if (index === correctIndex) {
          btn.classList.add('correct');
        } else if (index === chosenIndex) {
          btn.classList.add('wrong');
        }
      }
      if (chosenIndex === correctIndex) {
        score++;
        scoreEl.textContent = `Score: ${score}`;
      }
      nextBtn.disabled = false;
    }

    optionsEl.addEventListener('click', (event) => {
      const btn = event.target.closest('.option');
      if (!btn || btn.disabled) return;
      revealAnswer(Number(btn.dataset.index));
    });

    nextBtn.addEventListener('click', () => {
      current++;
      if (current < questions.length) {
        showQuestion();
      } else {
        progressEl.textContent = 'Finished';
        questionEl.textContent = `Quiz finished! You scored ${score} out of ${questions.length}.`;
        optionsEl.innerHTML = '';
        nextBtn.disabled = true;
      }
    });

    showQuestion();
  </script>
</body>
</html>
  • TIME_LIMIT is a constant written in capitals, a common way to mark a fixed setting. Change it to 5 to test the timer quickly.
  • timerId stores the id from setInterval, so any function can stop the timer.
  • startTimer() first calls clearInterval(timerId). This makes sure an old timer is never left running when a new question starts.
  • Every second, timeLeft-- takes away one and updates the label. At 0 we show “Time’s up!” and call revealAnswer(null), which shows the right answer and unlocks Next.
  • revealAnswer now starts with clearInterval(timerId), so answering stops the clock.
Why stop the timer? An interval keeps running until you clear it, even when the question has changed. Two timers running together would make the clock count down twice as fast. See Common Mistakes below.

Step 7: The Result Screen and Play Again

The final step turns the page into two screens. Both are <section> elements, and we show one at a time with the hidden property. This is the complete app.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
    .big { font-size: 2.5rem; font-weight: 700; color: #4f46e5; margin: 8px 0; }
    [hidden] { display: none !important; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>

    <section id="quiz-screen">
      <div class="top">
        <span id="progress"></span>
        <span id="timer">Time: 15s</span>
        <span id="score">Score: 0</span>
      </div>
      <p class="question" id="question"></p>
      <div class="options" id="options"></div>
      <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
    </section>

    <section id="result-screen" hidden>
      <p>Your result</p>
      <p class="big" id="result-percent"></p>
      <p id="result-text"></p>
      <p id="result-message"></p>
      <button type="button" class="main-btn" id="play-again">Play again</button>
    </section>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');
    const progressEl = document.querySelector('#progress');
    const nextBtn = document.querySelector('#next-btn');
    const scoreEl = document.querySelector('#score');
    const timerEl = document.querySelector('#timer');
    const quizScreen = document.querySelector('#quiz-screen');
    const resultScreen = document.querySelector('#result-screen');
    const resultPercent = document.querySelector('#result-percent');
    const resultText = document.querySelector('#result-text');
    const resultMessage = document.querySelector('#result-message');
    const playAgainBtn = document.querySelector('#play-again');

    const TIME_LIMIT = 15;
    let current = 0;
    let score = 0;
    let timeLeft = TIME_LIMIT;
    let timerId = null;

    function showQuestion() {
      const q = questions[current];
      progressEl.textContent = `Question ${current + 1} of ${questions.length}`;
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
      nextBtn.disabled = true;
      nextBtn.textContent = current === questions.length - 1 ? 'See result' : 'Next';
      startTimer();
    }

    function startTimer() {
      clearInterval(timerId);
      timeLeft = TIME_LIMIT;
      timerEl.textContent = `Time: ${timeLeft}s`;

      timerId = setInterval(() => {
        timeLeft--;
        timerEl.textContent = `Time: ${timeLeft}s`;
        if (timeLeft === 0) {
          timerEl.textContent = "Time's up!";
          revealAnswer(null);
        }
      }, 1000);
    }

    function revealAnswer(chosenIndex) {
      clearInterval(timerId);
      const correctIndex = questions[current].answer;
      for (const btn of optionsEl.children) {
        const index = Number(btn.dataset.index);
        btn.disabled = true;
        if (index === correctIndex) {
          btn.classList.add('correct');
        } else if (index === chosenIndex) {
          btn.classList.add('wrong');
        }
      }
      if (chosenIndex === correctIndex) {
        score++;
        scoreEl.textContent = `Score: ${score}`;
      }
      nextBtn.disabled = false;
    }

    function showResult() {
      clearInterval(timerId);
      const percent = Math.round((score / questions.length) * 100);
      resultPercent.textContent = `${percent}%`;
      resultText.textContent = `You answered ${score} out of ${questions.length} correctly.`;

      if (percent >= 80) {
        resultMessage.textContent = 'Excellent! You are exam ready.';
      } else if (percent >= 50) {
        resultMessage.textContent = 'Good effort. Revise once more and try again.';
      } else {
        resultMessage.textContent = 'Keep practising. Every attempt makes you better.';
      }

      quizScreen.hidden = true;
      resultScreen.hidden = false;
    }

    optionsEl.addEventListener('click', (event) => {
      const btn = event.target.closest('.option');
      if (!btn || btn.disabled) return;
      revealAnswer(Number(btn.dataset.index));
    });

    nextBtn.addEventListener('click', () => {
      current++;
      if (current < questions.length) {
        showQuestion();
      } else {
        showResult();
      }
    });

    playAgainBtn.addEventListener('click', () => {
      current = 0;
      score = 0;
      scoreEl.textContent = 'Score: 0';
      resultScreen.hidden = true;
      quizScreen.hidden = false;
      showQuestion();
    });

    showQuestion();
  </script>
</body>
</html>
  • Math.round((score / questions.length) * 100) turns, for example, 4 out of 5 into 80. Math.round removes decimals: with 3 questions, 2 right gives 66.67, which becomes 67.
  • An if...else if chain picks a friendly message for the percentage.
  • quizScreen.hidden = true hides the quiz, and resultScreen.hidden = false shows the result. The CSS rule [hidden] { display: none !important; } makes sure no other style can show a hidden screen by mistake.
  • Play again resets every piece of state: current, score and the score label. Then it swaps the screens back and calls showQuestion(), which starts a fresh timer.

Real-Life Example: Rahul’s Morning Practice

Every morning on the bus, Rahul opens the quiz. He gets the capital and the planet right, misses the rupee symbol year (it was adopted in 2010), and runs out of time on one question. The result screen shows 60% and tells him to revise once more. He presses Play again, and this time scores 100%. That is the full app from Step 7 doing its job.

Try these changes:

  1. Add a sixth question of your own to the array. The progress label and the percentage update by themselves.
  2. Set TIME_LIMIT to 10 for a harder quiz.
  3. Change the messages on the result screen to suit your exam.

Ideas to Take It Further

  • Shuffle the questions each time Rahul plays, so he cannot just remember the order.
  • Review screen: keep an array of wrong answers and list them, with the correct option, on the result screen.
  • Categories: add a topic property (History, Science) and let the learner pick one before starting.
  • Keyboard support: let the keys 1 to 4 choose an option.

Common Mistakes

  • Forgetting clearInterval. Each call to setInterval creates a new timer. If the old one is not stopped, both run, and the clock jumps by two each second. Run this small example to see two timers adding up:
let ticks = 0;

// Each call to start() makes a NEW interval. Forgetting clearInterval
// means the old one keeps running too.
function start() {
  setInterval(() => {
    ticks++;
  }, 100);
}

start();
start(); // a second timer, without stopping the first

setTimeout(() => {
  console.log(`Ticks after 1 second: ${ticks}`); // about 20, not 10
}, 1050);
  • Counting the answer index from 1. If you write answer: 3 for the third option, the quiz marks the wrong button as correct. Arrays start at 0.
  • Allowing many clicks on one question. Without disabling the buttons, Rahul could click every option and collect points. Fix: disable all options inside revealAnswer.
  • Not resetting everything on Play again. If you reset current but not score, the second round starts with the old score. Reset every state variable in one place.

Practice Task: Best Score and a Red Timer

Rahul wants to beat himself every day. Add two features to the complete app:

  • Keep the best percentage so far in a variable, and show “Best score so far: 80%” on the result screen. Use Math.max.
  • Make the timer red when 5 seconds or less are left, using the ready warning class and classList.toggle.
  • Make sure the timer is not red at the start of the next question.

The HTML and CSS are ready. Complete the three TODO comments.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
    .big { font-size: 2.5rem; font-weight: 700; color: #4f46e5; margin: 8px 0; }
    [hidden] { display: none !important; }
    .warning { color: #dc2626; font-weight: 700; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>

    <section id="quiz-screen">
      <div class="top">
        <span id="progress"></span>
        <span id="timer">Time: 15s</span>
        <span id="score">Score: 0</span>
      </div>
      <p class="question" id="question"></p>
      <div class="options" id="options"></div>
      <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
    </section>

    <section id="result-screen" hidden>
      <p>Your result</p>
      <p class="big" id="result-percent"></p>
      <p id="result-text"></p>
      <p id="result-message"></p>
      <p id="best"></p>
      <button type="button" class="main-btn" id="play-again">Play again</button>
    </section>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');
    const progressEl = document.querySelector('#progress');
    const nextBtn = document.querySelector('#next-btn');
    const scoreEl = document.querySelector('#score');
    const timerEl = document.querySelector('#timer');
    const quizScreen = document.querySelector('#quiz-screen');
    const resultScreen = document.querySelector('#result-screen');
    const resultPercent = document.querySelector('#result-percent');
    const resultText = document.querySelector('#result-text');
    const resultMessage = document.querySelector('#result-message');
    const playAgainBtn = document.querySelector('#play-again');
    const bestEl = document.querySelector('#best');

    const TIME_LIMIT = 15;
    let current = 0;
    let score = 0;
    let timeLeft = TIME_LIMIT;
    let timerId = null;
    // TODO 1: add a variable for the best percentage, starting at 0.

    function showQuestion() {
      const q = questions[current];
      progressEl.textContent = `Question ${current + 1} of ${questions.length}`;
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
      nextBtn.disabled = true;
      nextBtn.textContent = current === questions.length - 1 ? 'See result' : 'Next';
      startTimer();
    }

    function startTimer() {
      clearInterval(timerId);
      timeLeft = TIME_LIMIT;
      timerEl.textContent = `Time: ${timeLeft}s`;

      timerId = setInterval(() => {
        timeLeft--;
        timerEl.textContent = `Time: ${timeLeft}s`;
        // TODO 2: add the 'warning' class when 5 seconds or less are left.
        if (timeLeft === 0) {
          timerEl.textContent = "Time's up!";
          revealAnswer(null);
        }
      }, 1000);
    }

    function revealAnswer(chosenIndex) {
      clearInterval(timerId);
      const correctIndex = questions[current].answer;
      for (const btn of optionsEl.children) {
        const index = Number(btn.dataset.index);
        btn.disabled = true;
        if (index === correctIndex) {
          btn.classList.add('correct');
        } else if (index === chosenIndex) {
          btn.classList.add('wrong');
        }
      }
      if (chosenIndex === correctIndex) {
        score++;
        scoreEl.textContent = `Score: ${score}`;
      }
      nextBtn.disabled = false;
    }

    function showResult() {
      clearInterval(timerId);
      const percent = Math.round((score / questions.length) * 100);
      resultPercent.textContent = `${percent}%`;
      resultText.textContent = `You answered ${score} out of ${questions.length} correctly.`;

      if (percent >= 80) {
        resultMessage.textContent = 'Excellent! You are exam ready.';
      } else if (percent >= 50) {
        resultMessage.textContent = 'Good effort. Revise once more and try again.';
      } else {
        resultMessage.textContent = 'Keep practising. Every attempt makes you better.';
      }

      // TODO 3: update the best percentage and show it in bestEl.
      quizScreen.hidden = true;
      resultScreen.hidden = false;
    }

    optionsEl.addEventListener('click', (event) => {
      const btn = event.target.closest('.option');
      if (!btn || btn.disabled) return;
      revealAnswer(Number(btn.dataset.index));
    });

    nextBtn.addEventListener('click', () => {
      current++;
      if (current < questions.length) {
        showQuestion();
      } else {
        showResult();
      }
    });

    playAgainBtn.addEventListener('click', () => {
      current = 0;
      score = 0;
      scoreEl.textContent = 'Score: 0';
      resultScreen.hidden = true;
      quizScreen.hidden = false;
      showQuestion();
    });

    showQuestion();
  </script>
</body>
</html>
Show one possible answer (try it yourself first!)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GK Quiz</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #eef2ff; color: #1f2937; margin: 0; padding: 24px 16px; }
    .quiz { max-width: 460px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 20px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); }
    h1 { font-size: 1.3rem; margin: 0 0 12px; }
    .top { display: flex; justify-content: space-between; color: #6b7280; font-size: 0.95rem; }
    .question { font-size: 1.15rem; font-weight: 600; margin: 16px 0; }
    .options { display: grid; gap: 10px; margin-bottom: 16px; }
    .option { text-align: left; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 1rem; cursor: pointer; }
    .option:hover:not(:disabled) { border-color: #4f46e5; }
    .option:disabled { cursor: default; color: #1f2937; }
    .option.correct { background: #dcfce7; border-color: #16a34a; }
    .option.wrong { background: #fee2e2; border-color: #dc2626; }
    .main-btn { background: #4f46e5; color: #ffffff; border: none; border-radius: 8px; padding: 10px 18px; font-size: 1rem; cursor: pointer; }
    .main-btn:disabled { opacity: 0.5; cursor: not-allowed; }
    .big { font-size: 2.5rem; font-weight: 700; color: #4f46e5; margin: 8px 0; }
    [hidden] { display: none !important; }
    .warning { color: #dc2626; font-weight: 700; }
  </style>
</head>
<body>
  <div class="quiz">
    <h1>GK Practice Quiz</h1>

    <section id="quiz-screen">
      <div class="top">
        <span id="progress"></span>
        <span id="timer">Time: 15s</span>
        <span id="score">Score: 0</span>
      </div>
      <p class="question" id="question"></p>
      <div class="options" id="options"></div>
      <button type="button" class="main-btn" id="next-btn" disabled>Next</button>
    </section>

    <section id="result-screen" hidden>
      <p>Your result</p>
      <p class="big" id="result-percent"></p>
      <p id="result-text"></p>
      <p id="result-message"></p>
      <p id="best"></p>
      <button type="button" class="main-btn" id="play-again">Play again</button>
    </section>
  </div>

  <script>
    const questions = [
      {
        question: 'What is the capital of India?',
        options: ['Mumbai', 'Kolkata', 'New Delhi', 'Chennai'],
        answer: 2
      },
      {
        question: 'What is the national animal of India?',
        options: ['Asiatic lion', 'Bengal tiger', 'Indian elephant', 'Peacock'],
        answer: 1
      },
      {
        question: 'In which year was the Indian rupee symbol adopted?',
        options: ['2005', '2008', '2010', '2014'],
        answer: 2
      },
      {
        question: 'Which is the largest planet in our solar system?',
        options: ['Earth', 'Saturn', 'Mars', 'Jupiter'],
        answer: 3
      },
      {
        question: 'At what temperature does water boil at sea level?',
        options: ['50 °C', '90 °C', '100 °C', '120 °C'],
        answer: 2
      }
    ];

    const questionEl = document.querySelector('#question');
    const optionsEl = document.querySelector('#options');
    const progressEl = document.querySelector('#progress');
    const nextBtn = document.querySelector('#next-btn');
    const scoreEl = document.querySelector('#score');
    const timerEl = document.querySelector('#timer');
    const quizScreen = document.querySelector('#quiz-screen');
    const resultScreen = document.querySelector('#result-screen');
    const resultPercent = document.querySelector('#result-percent');
    const resultText = document.querySelector('#result-text');
    const resultMessage = document.querySelector('#result-message');
    const playAgainBtn = document.querySelector('#play-again');
    const bestEl = document.querySelector('#best');

    const TIME_LIMIT = 15;
    let current = 0;
    let score = 0;
    let timeLeft = TIME_LIMIT;
    let timerId = null;
    let bestPercent = 0;

    function showQuestion() {
      const q = questions[current];
      progressEl.textContent = `Question ${current + 1} of ${questions.length}`;
      questionEl.textContent = q.question;
      optionsEl.innerHTML = '';
      q.options.forEach((option, index) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'option';
        btn.textContent = option;
        btn.dataset.index = index;
        optionsEl.append(btn);
      });
      nextBtn.disabled = true;
      nextBtn.textContent = current === questions.length - 1 ? 'See result' : 'Next';
      startTimer();
    }

    function startTimer() {
      clearInterval(timerId);
      timeLeft = TIME_LIMIT;
      timerEl.textContent = `Time: ${timeLeft}s`;
      timerEl.classList.remove('warning');

      timerId = setInterval(() => {
        timeLeft--;
        timerEl.textContent = `Time: ${timeLeft}s`;
        timerEl.classList.toggle('warning', timeLeft <= 5);
        if (timeLeft === 0) {
          timerEl.textContent = "Time's up!";
          revealAnswer(null);
        }
      }, 1000);
    }

    function revealAnswer(chosenIndex) {
      clearInterval(timerId);
      const correctIndex = questions[current].answer;
      for (const btn of optionsEl.children) {
        const index = Number(btn.dataset.index);
        btn.disabled = true;
        if (index === correctIndex) {
          btn.classList.add('correct');
        } else if (index === chosenIndex) {
          btn.classList.add('wrong');
        }
      }
      if (chosenIndex === correctIndex) {
        score++;
        scoreEl.textContent = `Score: ${score}`;
      }
      nextBtn.disabled = false;
    }

    function showResult() {
      clearInterval(timerId);
      const percent = Math.round((score / questions.length) * 100);
      resultPercent.textContent = `${percent}%`;
      resultText.textContent = `You answered ${score} out of ${questions.length} correctly.`;

      if (percent >= 80) {
        resultMessage.textContent = 'Excellent! You are exam ready.';
      } else if (percent >= 50) {
        resultMessage.textContent = 'Good effort. Revise once more and try again.';
      } else {
        resultMessage.textContent = 'Keep practising. Every attempt makes you better.';
      }

      bestPercent = Math.max(bestPercent, percent);
      bestEl.textContent = `Best score so far: ${bestPercent}%`;
      quizScreen.hidden = true;
      resultScreen.hidden = false;
    }

    optionsEl.addEventListener('click', (event) => {
      const btn = event.target.closest('.option');
      if (!btn || btn.disabled) return;
      revealAnswer(Number(btn.dataset.index));
    });

    nextBtn.addEventListener('click', () => {
      current++;
      if (current < questions.length) {
        showQuestion();
      } else {
        showResult();
      }
    });

    playAgainBtn.addEventListener('click', () => {
      current = 0;
      score = 0;
      scoreEl.textContent = 'Score: 0';
      resultScreen.hidden = true;
      quizScreen.hidden = false;
      showQuestion();
    });

    showQuestion();
  </script>
</body>
</html>

Key Takeaways

  • Store quiz questions as an array of objects, with the right answer saved as an index.
  • Keep app state in a few clear variables (current, score, timeLeft, timerId) and build the screen from them.
  • One function, revealAnswer, handles both a click and a timeout, so the scoring logic is never repeated.
  • setInterval returns an id. Always stop the old timer with clearInterval before starting a new one.
  • Switch screens with the hidden property, and reset every state variable when the user plays again.

❓ Frequently Asked Questions

Yes. Add more objects to the questions array. The progress label, the score and the percentage all use questions.length, so they adjust by themselves.
No. The browser runs the function as close to every second as it can, but it can be slightly late when the page is busy or the tab is in the background. For a quiz timer, this small delay does not matter.
setInterval repeats every second until you stop it, which suits a countdown. setTimeout runs only once, so you would need to start it again every second.
Yes. Anything in front-end JavaScript can be read in the browser tools. For a practice quiz this is fine. A real exam website checks answers on a server.
Was this lesson helpful?

🎯 Finished this lesson?

🔗 Related Tutorials
🤖 Got a Doubt? Ask AI
Get instant answers in Hindi or English
🤖
Z2T Bot
Online
Ask me anything about this tutorial — I'm here! 👋