Build a Terminal Quiz Game in Python

Published on: July 10, 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

    Jogo da forca para iniciantes desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Hangman Game in Python

    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

    Build a Number Guessing Game in Python

    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
    Desenhos para iniciantes usando Turtle em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Python Turtle Drawing: Complete Beginner Guide

    Learn Python Turtle drawing from scratch: move the turtle, draw shapes, use colors, handle keys, and create a complete geometric

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Criação de jogos para iniciantes usando Pygame em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Pygame for Beginners: Build Your First Game

    Learn Pygame from scratch: install it, create a window, handle events, move a player, detect collisions, add scoring, and build

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026
    Ilustração de uma cobra com o logo do Python centralizado
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Snake Game in Python with Turtle

    Build a complete Snake game in Python with Turtle. Add movement, food, collisions, body growth, scoring, and keyboard controls.

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026
    Projeto clássico do jogo Pong desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build Your First Pong Game with Python

    Build your first Pong game with Python and the Turtle module: window setup, paddles, ball physics, collision detection, scoreboard, and

    Ler mais

    Tempo de leitura: 7 minutos
    03/06/2026