How to Implement a Quiz App with Timer and Score Tracking in Vanilla JavaScript
You can build a fully functional quiz application with per-question timers, visual progress indicators, and persistent score tracking using only vanilla JavaScript, CSS, and HTML, as demonstrated in the jisan-mia/dom-projects repository.
The jisan-mia/dom-projects repository contains two complete implementations that show how to implement a quiz app with timer and score tracking without external dependencies. Both versions use plain DOM manipulation, setInterval for countdown logic, and localStorage for data persistence, making them suitable for static hosting or educational purposes.
Core Architecture of the Quiz Implementations
The repository provides two distinct approaches located in separate directories:
| Component | Simple Quiz (projects/simple-quiz-app/) |
Advanced Quiz (projects/advanced-quiz-app/) |
|---|---|---|
| Timer | Per-question countdown using setInterval in script.js |
Enhanced timer with configurable duration |
| Progress | Visual dots (.prog class) colored green/red per question |
Progress bar (#progressBarFull) showing percentage complete |
| Scoring | Integer counter converted to percentage in scoreRender() |
Real-time score display with localStorage persistence |
| Data Source | Hardcoded questions array |
Dynamic fetch from Open Trivia DB API |
Building the Simple Quiz App with Timer and Score Tracking
The simple quiz implementation in projects/simple-quiz-app/ provides the minimal viable architecture for timed assessments.
Implementing the Per-Question Timer
The timer logic resides in script.js and uses setInterval to update both a numeric counter and a visual gauge every second:
// Timer configuration constants
const questionTime = 10; // seconds per question
const gaugeWidth = 300; // pixels
const gaugeUnit = gaugeWidth / questionTime;
let TIMER = setInterval(renderCounter, 1000);
function renderCounter() {
if (count <= questionTime) {
counter.innerHTML = count;
timeGauge.style.width = count * gaugeUnit + "px";
count++;
} else {
// Time expired: mark as wrong and advance
count = 0;
answerIsWrong();
moveToNextQuestionOrEnd();
}
}
When the counter exceeds questionTime, the application automatically marks the answer as incorrect and advances to the next question, ensuring the quiz maintains its timing constraints.
Tracking and Displaying the Score
Score tracking occurs in the checkAnswer function, which compares the user's selection against the correct property of the current question object:
function checkAnswer(answer) {
if (answer == questions[runningQuestion].correct) {
score++; // Increment score
answerIsCorrect(); // Visual feedback: green dot
} else {
answerIsWrong(); // Visual feedback: red dot
}
count = 0; // Reset timer for next question
moveToNextQuestionOrEnd();
}
The final percentage calculation and display logic resides in scoreRender:
function scoreRender() {
const scorePercent = Math.round(100 * (score / questions.length));
// Select performance-based image
const scoreImg = scorePercent >= 80 ? "img/5.png"
: scorePercent >= 60 ? "img/4.png"
: scorePercent >= 40 ? "img/3.png"
: scorePercent >= 20 ? "img/2.png"
: "img/1.png";
scoreDiv.innerHTML = `<img src="${scoreImg}" /> <p>${scorePercent}%</p>`;
scoreDiv.style.display = "block";
}
Visual Progress Indicators
The simple quiz uses a series of <div> elements with the class prog to create progress dots. The functions answerIsCorrect and answerIsWrong modify the background color of the current dot to green (#0f0) or red (#f00), providing immediate visual feedback on performance.
Extending to the Advanced Quiz App
The projects/advanced-quiz-app/ directory contains a more sophisticated implementation that builds upon the simple quiz foundation.
Fetching Questions from Open Trivia DB
Unlike the hardcoded array in the simple version, the advanced quiz dynamically retrieves questions from the Open Trivia Database API:
// Configuration for API request
const API_URL = `https://opentdb.com/api.php?amount=${questionAmount}&category=${category}&difficulty=${difficulty}&type=multiple`;
fetchQuestions = async () => {
const response = await fetch(API_URL);
const loadedQuestions = await response.json();
// Process and randomize answer order
questions = loadedQuestions.results.map(processQuestionData);
};
Enhanced Progress Tracking and localStorage Persistence
The advanced version replaces the dot indicators with a continuous progress bar and implements persistent high-score storage:
// Progress bar update
progressBarFull.style.width = `${(questionCounter / maxQuestions) * 100}%`;
// Score persistence
localStorage.setItem('mostRecentScore', score);
// High score management
const highScores = JSON.parse(localStorage.getItem('highScores')) || [];
highScores.push({ name: username, score: mostRecentScore });
highScores.sort((a, b) => b.score - a.score);
highScores.splice(5); // Keep top 5
localStorage.setItem('highScores', JSON.stringify(highScores));
Key Implementation Files
| File Path | Purpose |
|---|---|
projects/simple-quiz-app/index.html |
Static structure for start screen, quiz interface, and results |
projects/simple-quiz-app/style.css |
Timer gauge (#timeGauge) and progress dot (.prog) styling |
projects/simple-quiz-app/script.js |
Core logic: renderCounter, checkAnswer, scoreRender |
projects/advanced-quiz-app/index.html |
Multi-page UI with home, game, end, and high-score screens |
projects/advanced-quiz-app/style.css |
Progress bar and high-score table styling |
projects/advanced-quiz-app/script.js |
API integration, localStorage persistence, high-score sorting |
data.json |
Project metadata documenting timer and score features |
Summary
- Timer Implementation: Use
setIntervalwith a counter variable and visual gauge updated every second; automatically advance questions when time expires. - Score Tracking: Maintain an integer counter incremented on correct answers, then convert to a percentage for final display with performance-based visual feedback.
- Progress Indicators: Implement either dot-based (simple) or continuous bar (advanced) visualizers that update after each answer.
- Data Persistence: Use
localStorageto store recent scores and high-score tables across browser sessions, as shown in the advanced implementation. - Dynamic Content: Fetch questions from external APIs like Open Trivia DB and randomize answer order to create varied quiz experiences.
Frequently Asked Questions
How do you implement a countdown timer for each question in a JavaScript quiz?
Use setInterval to decrement or increment a counter every second while updating a visual gauge element. In projects/simple-quiz-app/script.js, the renderCounter function checks if the count exceeds questionTime (10 seconds); if so, it calls answerIsWrong() and advances to the next question, ensuring the quiz maintains strict timing per question.
What is the best way to track and calculate quiz scores in vanilla JavaScript?
Maintain a global score variable initialized to zero, increment it within your answer validation logic when the user's selection matches the correct answer, then calculate the percentage by dividing by the total questions. The scoreRender function in the simple quiz demonstrates this by computing Math.round(100 * (score / questions.length)) and displaying a performance-based image alongside the percentage.
How can you persist high scores across browser sessions in a quiz application?
Use the Web Storage API to save scores to localStorage. In the advanced quiz implementation, when the game ends, the code executes localStorage.setItem('mostRecentScore', score) to save the current attempt. For high-score tables, retrieve existing scores with JSON.parse(localStorage.getItem('highScores')), append the new result, sort by score in descending order, limit to the top entries (e.g., 5), and write the array back to localStorage using JSON.stringify.
What are the differences between the simple and advanced quiz implementations in the dom-projects repository?
The simple quiz (projects/simple-quiz-app/) uses a hardcoded questions array, visual progress dots that change color based on correctness, and a basic timer gauge, making it ideal for learning fundamental DOM manipulation. The advanced quiz (projects/advanced-quiz-app/) fetches questions dynamically from the Open Trivia DB API, features a continuous progress bar, implements localStorage for high-score persistence, and includes a multi-page interface with category/difficulty selection, demonstrating production-ready patterns for scalable quiz applications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →