I'm working through a roadmap toward becoming a software engineer, and step one is building projects with plain HTML, CSS, and JavaScript before reaching for frameworks. Part of how I'm holding myself accountable is shipping and writing about one project a week — this quiz game is the first entry in that series.
No React, no build tools, no dependencies. Just the fundamental
What it does
It's a three-screen quiz: a start screen, a quiz screen with five general-knowledge questions, and a results screen with a score and a message based on how you did. Pick an answer and you instantly see the correct one highlighted in green, and your pick in red if you got it wrong.
How the screens work
Instead of separate HTML pages, the whole thing lives in one index.html with three .screen divs. Only one is visible at a time, controlled by adding/removing an active class:
`css
.screen {
display: none;
padding: 2rem;
text-align: center;
}
.screen.active {
display: block;
}`
Switching screens in JS is just a classList toggle:
`js
startScreen.classList.remove("active");
quizScreen.classList.add("active");
`
It's a simple pattern, but it's basically a mini single-page app without any routing library.
Rendering questions dynamically
The questions live in an array of objects, each with a question string and an answers array where one option is flagged correct: true:
`javascript
`js
{
question: "What is the capital of France?",
answers: [
{ text: "London", correct: false },
{ text: "Berlin", correct: false },
{ text: "Paris", correct: true },
{ text: "Madrid", correct: false },
],
}`
For each question, I clear the answers container and generate a button per answer, stashing the correct flag on the button's dataset so I can check it on click without a lookup. This means adding, removing, or editing questions is just editing the array — thequestion counter, progress bar, andmax score all update automatically based on > quizQuestions.length.
Bugs I ran into:
An accidentally nested function
At one point I had a function declared inside another function without meaning to — I'd started editing one function, gotten distracted mid-thought, and closed the braces in the wrong place. The code still ran, which made it worse: no error, just quietly wrong behavior. It took me a while to notice because I was staring at the inner function's logic assuming the bug was there, when the real problem was scope — the outer function wasn't calling the inner one the way I expected, and variables weren't behaving the way I thought they should. Once I actually read the brace structure carefully instead of the logic, it was obvious. Lesson: when behavior is weird and the logic "looks right," check the shape of the code before the content of it.
Missing commas
Small, dumb, and it cost me more time than it should have: I dropped commas between object properties and array items in a couple of places. Sometimes JS threw a clear syntax error; other times it just silently broke things or pointed at the wrong line. Now I write one item per line and let my editor's formatting catch it before I even run the code.
Locking answers during the reveal
One thing I didn't think about until I hit it: what stops someone from spam-clicking multiple answers before the "correct" highlight even shows up? I added a simple boolean flag, answerDisabled, that gets set to true the moment an answer is picked and reset to false when the next question loads:
javascript
``js
function selectAnswer(event) {
if (answerDisabled) return;
answerDisabled = true;
// ...reveal correct/incorrect, update score...
setTimeout(() => {
currentQuestionIndex++;
if (currentQuestionIndex < quizQuestions.length) {
showQuestion();
} else {
showResult();
}
}, 1000);
}`
It's a small thing, but it's the kind of bug you only notice by actually clicking around your own app like a user would, not just reading the code.
Scoring and the result message
At the end, I calculate a percentage and match it against a few thresholds to pick a message — nothing fancy, just an if/else chain:
`js
const percentage = (score / quizQuestions.length) * 100;
if (percentage === 100) {
resultMessage.textContent = "Perfect! You're a genius!";
} else if (percentage >= 80) {
resultMessage.textContent = "Great job! You know your stuff!";
} else if (percentage >= 60) {
resultMessage.textContent = "Good effort! Keep learning!";
} // ...and so on`
`
Nothing here is groundbreaking but building it without a framework was a good way to get more comfortable with vanilla DOM manipulation, state management, and catching my own scoping and syntax mistakes before shipping. This is project one of my weekly series — next week I'll have another one up.
If you've built something similar, hit a similar nested-function or missing-comma bug, or have ideas for what I should add next, I'd love to hear it in the comments.
Check out the full code on GitHub



Top comments (0)