Python Terminal Quiz: Questions and Scoring

Updated on: August 21, 2026
Reading time: 4 minutes
Quiz interativo no terminal desenvolvido com Python

A terminal quiz is an ideal beginner project because it transforms basic Python concepts into a complete interactive program. Questions are stored as data, the user selects answers, the program validates input, points are calculated, and a final result is displayed.

This tutorial builds a complete terminal quiz game in Python. The final version includes multiple-choice questions, shuffled options, input validation, explanations, scoring, replay, optional JSON loading, and small functions that are easy to test.

The project relies on lists and dictionaries. Review the Python lists guide and Python data types guide when those structures are new to you.

Plan the Quiz Data

Each question needs a prompt, a collection of options, the correct answer, and optionally an explanation. A dictionary represents one question, and a list stores the complete quiz.

QUESTIONS = [
    {
        "prompt": "Which keyword defines a function in Python?",
        "options": ["class", "def", "return", "import"],
        "answer": "def",
        "explanation": "Python functions begin with the def keyword.",
    },
    {
        "prompt": "Which type stores unique values?",
        "options": ["list", "tuple", "set", "str"],
        "answer": "set",
        "explanation": "A set keeps unique hashable elements.",
    },
]

Storing the answer as the actual text makes option shuffling easier. An answer letter such as B would become incorrect after the options change order.

Display One Question

Use enumerate() to create numbered choices:

def display_question(question, number, total):
    print()
    print(f"Question {number} of {total}")
    print(question["prompt"])

    for option_number, option in enumerate(
        question["options"],
        start=1,
    ):
        print(f"  {option_number}. {option}")

The detailed Python enumerate guide explains how the custom starting value works.

Read and Validate an Answer

The built-in input() function returns text. The official Python input documentation describes its behavior.

def read_option(number_of_options):
    while True:
        raw_answer = input("Your answer: ").strip()

        try:
            selected = int(raw_answer)
        except ValueError:
            print("Enter the number of an option.")
            continue

        if 1 <= selected <= number_of_options:
            return selected - 1

        print(f"Choose a number from 1 to {number_of_options}.")

The function returns a zero-based list index. Invalid input does not end the program and does not count as an answered question.

Check the Answer

def is_correct(question, selected_index):
    selected_text = question["options"][selected_index]
    return selected_text == question["answer"]

This pure function is easy to test because it does not read input or print output.

Shuffle Questions and Options

Use a dedicated random generator so a fixed seed can reproduce a quiz during tests.

import random


def prepare_questions(questions, generator=None):
    generator = generator or random.Random()
    prepared = []

    for question in questions:
        copied_question = {
            **question,
            "options": list(question["options"]),
        }
        generator.shuffle(copied_question["options"])
        prepared.append(copied_question)

    generator.shuffle(prepared)
    return prepared

Copying prevents the original data from being permanently reordered. The Python random module guide covers shuffling and deterministic seeds.

Calculate a Percentage

def calculate_percentage(score, total):
    if total == 0:
        return 0.0

    return score / total * 100

Handling an empty quiz avoids division by zero. The function uses a clear return value rather than modifying a global variable.

Complete Terminal Quiz Game

import random

QUESTIONS = [
    {
        "prompt": "Which keyword defines a function in Python?",
        "options": ["class", "def", "return", "import"],
        "answer": "def",
        "explanation": "Python functions begin with the def keyword.",
    },
    {
        "prompt": "Which type stores unique values?",
        "options": ["list", "tuple", "set", "str"],
        "answer": "set",
        "explanation": "A set keeps unique hashable elements.",
    },
    {
        "prompt": "What does len([10, 20, 30]) return?",
        "options": ["2", "3", "30", "60"],
        "answer": "3",
        "explanation": "len() returns the number of list elements.",
    },
    {
        "prompt": "Which statement handles an exception?",
        "options": ["if/else", "try/except", "for/in", "match/case"],
        "answer": "try/except",
        "explanation": "try/except handles exceptions raised by code.",
    },
    {
        "prompt": "Which file extension normally stores Python code?",
        "options": [".js", ".html", ".py", ".csv"],
        "answer": ".py",
        "explanation": "Python source files normally use the .py extension.",
    },
]


def prepare_questions(questions, generator=None):
    generator = generator or random.Random()
    prepared = []

    for question in questions:
        copied_question = {
            **question,
            "options": list(question["options"]),
        }
        generator.shuffle(copied_question["options"])
        prepared.append(copied_question)

    generator.shuffle(prepared)
    return prepared


def display_question(question, number, total):
    print()
    print(f"Question {number} of {total}")
    print(question["prompt"])

    for option_number, option in enumerate(
        question["options"],
        start=1,
    ):
        print(f"  {option_number}. {option}")


def read_option(number_of_options):
    while True:
        raw_answer = input("Your answer: ").strip()

        try:
            selected = int(raw_answer)
        except ValueError:
            print("Enter the number of an option.")
            continue

        if 1 <= selected <= number_of_options:
            return selected - 1

        print(f"Choose a number from 1 to {number_of_options}.")


def is_correct(question, selected_index):
    selected_text = question["options"][selected_index]
    return selected_text == question["answer"]


def calculate_percentage(score, total):
    if total == 0:
        return 0.0

    return score / total * 100


def play_quiz(questions, generator=None):
    prepared = prepare_questions(questions, generator)
    score = 0

    for number, question in enumerate(prepared, start=1):
        display_question(question, number, len(prepared))
        selected_index = read_option(len(question["options"]))

        if is_correct(question, selected_index):
            score += 1
            print("Correct!")
        else:
            selected = question["options"][selected_index]
            print(f"Not quite. You chose: {selected}")
            print(f"Correct answer: {question['answer']}")

        explanation = question.get("explanation")
        if explanation:
            print(explanation)

    percentage = calculate_percentage(score, len(prepared))
    print()
    print("Quiz complete")
    print(f"Score: {score}/{len(prepared)}")
    print(f"Percentage: {percentage:.1f}%")

    if percentage == 100:
        print("Perfect score!")
    elif percentage >= 70:
        print("Great work!")
    elif percentage >= 50:
        print("Good start. Review the explanations and try again.")
    else:
        print("Keep practicing and play another round.")

    return score


def wants_to_play_again():
    while True:
        print()
        answer = input("Play again? [y/n]: ").strip().lower()

        if answer in {"y", "yes"}:
            return True

        if answer in {"n", "no"}:
            return False

        print("Enter y or n.")


def main():
    print("Python Terminal Quiz")

    while True:
        play_quiz(QUESTIONS)

        if not wants_to_play_again():
            print("Thanks for playing!")
            break


if __name__ == "__main__":
    main()

Save the file as quiz.py and run:

python quiz.py

Load Questions from JSON

Keeping questions outside the source code lets a teacher or editor update the quiz without changing its logic. Create questions.json:

[
  {
    "prompt": "Which keyword defines a function in Python?",
    "options": ["class", "def", "return", "import"],
    "answer": "def",
    "explanation": "Python functions begin with def."
  }
]

Load it with the standard-library json module:

import json
from pathlib import Path


def load_questions(path):
    path = Path(path)

    with path.open("r", encoding="utf-8") as file:
        questions = json.load(file)

    if not isinstance(questions, list):
        raise ValueError("The JSON root must be a list.")

    return questions

The official Python JSON documentation explains encoding and serialization behavior. The pathlib guide covers reliable file paths.

Validate External Question Data

JSON can be valid while still missing required quiz fields. Validate each question before starting:

def validate_questions(questions):
    required = {"prompt", "options", "answer"}

    for number, question in enumerate(questions, start=1):
        missing = required - question.keys()

        if missing:
            raise ValueError(
                f"Question {number} is missing: {sorted(missing)}"
            )

        if question["answer"] not in question["options"]:
            raise ValueError(
                f"Question {number} answer is not in its options"
            )

Failing early with a clear message is better than producing a mysterious error halfway through a quiz.

Add Command-Line Options

A larger version can accept a question file, question limit, or random seed:

import argparse


def build_parser():
    parser = argparse.ArgumentParser(
        description="Run a terminal quiz."
    )
    parser.add_argument("--file", default="questions.json")
    parser.add_argument("--limit", type=int)
    parser.add_argument("--seed", type=int)
    return parser

The argparse CLI guide explains validation, flags, and subcommands.

Test the Quiz Logic

Functions that do not call input() can be tested directly:

from quiz import calculate_percentage, is_correct


def test_calculate_percentage():
    assert calculate_percentage(4, 5) == 80.0
    assert calculate_percentage(0, 0) == 0.0


def test_is_correct():
    question = {
        "options": ["list", "set"],
        "answer": "set",
    }

    assert is_correct(question, 1) is True
    assert is_correct(question, 0) is False

The Python unit testing guide shows how to mock input and capture output when you want to test interactive functions too.

Common Mistakes

  • Storing only an answer letter and then shuffling the options.
  • Trusting external JSON without validating required fields.
  • Converting input to an integer without handling ValueError.
  • Using global variables for every part of the score and question state.
  • Shuffling the original data and making tests unpredictable.
  • Mixing question data, display logic, input, and scoring in one large loop.
  • Accepting an answer index outside the available option range.

Ideas to Improve the Game

  • Add categories and difficulty levels.
  • Select only a requested number of questions.
  • Save high scores to JSON or SQLite.
  • Add a time limit for each question.
  • Support true/false and free-text questions.
  • Color terminal output with an optional library.
  • Create a graphical version with Tkinter or Pygame.
  • Publish question packs separately from the program.

Frequently Asked Questions

Do I need an external package?

No. The complete version uses only the Python standard library.

Why use dictionaries for questions?

A dictionary gives clear names to related values such as the prompt, options, answer, and explanation.

Can the options be letters instead of numbers?

Yes. Generate labels with chr() or a predefined alphabet, then convert the selected letter back to an index.

How do I keep the same random order for a test?

Pass random.Random(seed) to prepare_questions() or play_quiz().

How do I add questions without changing Python code?

Store them in JSON, validate the loaded structure, and pass the resulting list into the game.

Conclusion

A terminal quiz game in Python brings together data structures, loops, validation, functions, randomization, files, and tests in one approachable project. Separating the quiz data from the game logic makes the program easier to expand and maintain.

Begin with three questions and a simple score. Once that version is reliable, add JSON files, categories, command-line options, and saved results one feature at a time.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python PDF search with RAG
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Python PDF Search with RAG

    Build Python PDF search with RAG, semantic retrieval, grounded answers, evaluation, and document security.

    Ler mais

    Tempo de leitura: 6 minutos
    23/07/2026
    Criação de chatbot com API da OpenAI usando Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    OpenAI Chatbot with Python: Responses API

    Build a Python chatbot with the OpenAI Responses API, secure environment variables, conversation memory, model configuration, and practical error handling.

    Ler mais

    Tempo de leitura: 7 minutos
    10/07/2026
    Chatbot simples em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Python Chatbot: Rules, Intents, and Responses

    Build a simple Python chatbot with rules, input normalization, intents, random replies, JSON data, conversation loops, tests, and practical extension

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Criptografia e segurança de dados em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Secure Password Generator in Python with secrets

    Build a secure password generator in Python with secrets, configurable character rules, a CLI, passphrases, validation, and practical tests.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Jogo da forca para iniciantes desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Hangman in Python: Logic and Validation

    Build a complete Hangman game in Python with random words, input validation, repeated-letter checks, lives, ASCII art, and replay support.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Jogo de adivinhação de números desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Python Number Guessing Game: Hints and Attempts

    Build a number guessing game in Python with random numbers, input validation, hints, limited attempts, replay support, and clean functions.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026