Build a quiz with one score update per question
This project combines arrays, DOM creation, form submission and explicit UI state. A question has text, answer options, one correct index and an explanation. The score changes only after a valid first submission; Next advances without scoring again.
HTML, inside the body
JavaScript Practice Quiz
quiz.js
const questions = [
{
text: 'Which declaration prevents reassignment of its binding?',
options: ['let', 'const', 'var'],
answer: 1,
explanation: 'const prevents rebinding; an object it references may still mutate.'
},
{
text: 'Which property safely inserts plain user text?',
options: ['innerHTML', 'outerHTML', 'textContent'],
answer: 2,
explanation: 'textContent treats the value as text instead of parsing HTML.'
},
{
text: 'What does [2, 3].map(value => value * 2) produce?',
options: ['[4, 6]', '5', '[2, 3]'],
answer: 0,
explanation: 'map returns a new array with one transformed result per visited element.'
}
];
const form = document.querySelector('#quiz');
const legend = document.querySelector('#question');
const options = document.querySelector('#options');
const progress = document.querySelector('#progress');
const feedback = document.querySelector('#feedback');
const check = document.querySelector('#check');
const next = document.querySelector('#next');
const restart = document.querySelector('#restart');
let index = 0;
let score = 0;
let answered = false;
function render() {
answered = false;
feedback.textContent = '';
options.replaceChildren();
if (index === questions.length) {
form.hidden = true;
restart.hidden = false;
progress.textContent = 'Quiz complete';
feedback.textContent = 'Score: ' + score + ' / ' + questions.length;
restart.focus();
return;
}
form.hidden = false;
restart.hidden = true;
check.disabled = false;
next.hidden = true;
const item = questions[index];
legend.textContent = item.text;
progress.textContent = 'Question ' + (index + 1) + ' of ' + questions.length;
item.options.forEach((text, optionIndex) => {
const row = document.createElement('p');
const label = document.createElement('label');
const input = document.createElement('input');
input.type = 'radio';
input.name = 'answer';
input.value = String(optionIndex);
input.required = true;
label.append(input, ' ', text);
row.append(label);
options.append(row);
});
}
form.addEventListener('submit', event => {
event.preventDefault();
if (answered) return;
const selected = form.querySelector('input[name="answer"]:checked');
if (!selected) {
feedback.textContent = 'Select one answer first.';
return;
}
const item = questions[index];
const correct = Number(selected.value) === item.answer;
if (correct) score += 1;
answered = true;
feedback.textContent = (correct ? 'Correct. ' : 'Incorrect. ')
+ 'Answer: ' + item.options[item.answer] + '. ' + item.explanation;
form.querySelectorAll('input[name="answer"]').forEach(input => {
input.disabled = true;
});
check.disabled = true;
next.hidden = false;
next.textContent = index === questions.length - 1 ? 'Show result' : 'Next';
next.focus();
});
next.addEventListener('click', () => {
if (!answered) return;
index += 1;
render();
if (index < questions.length) options.querySelector('input')?.focus();
});
restart.addEventListener('click', () => {
index = 0;
score = 0;
render();
options.querySelector('input')?.focus();
});
render();Optional CSS
.tech-lesson-content { font: 1rem/1.6 system-ui, sans-serif; max-width: 42rem; margin: 2rem auto; padding: 1rem; }
fieldset { border: 1px solid #64748b; padding: 1rem; }
button { padding: 0.6rem 1rem; margin-top: 0.75rem; }
:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }Test the behavior
- Choose const, textContent and [4,6]; expect 3/3.
- Choose all incorrect options; expect 0/3 with explanations.
- Attempt submission without selecting an option; no score or index should change.
- Try repeated answer submission; answered prevents double counting.
- Restart after completion; score and question number should reset.
- Complete the flow with keyboard only and check the announced feedback.
The answer key is visible in client source. This is a practice quiz, not a secure examination engine. For assessed exams, server-side question delivery, authorization, timing and evaluation require a separate design.