What you'll learn
- Combine input, loops, lists, dictionaries, and functions in one working program
- Structure a small program as a set of functions with one job each
- Keep data separate from logic so the quiz can grow without rewriting code
- Finish a project you can run, show, and extend
Concept
Every lesson so far taught one tool. A project is different: it is about choosing which tool to use where, and fitting them together. The quiz game is small enough to finish in one sitting and complete enough to show someone. It asks questions with input(), checks answers with if, keeps score with a for loop and an accumulator, and reports at the end with an f-string, which touches everything in the Fundamentals level.
Start with the data. Each question needs the text, the choices, and the right answer, so a dictionary with text, choices, and answer keys is the natural shape for one question, and a list of those dictionaries named QUESTIONS is the quiz. Keeping the questions as data at the top of the file means adding a question is one more entry, not one more block of code.
Then split the work into functions with one job each. ask shows a question and returns what the person typed. check compares the typed answer to the correct one and returns True or False. run_quiz loops over the questions, calls the other two, and accumulates the score. report turns the score into a message. Each function is short enough to read in one breath, and each can be tested on its own.

Watch one question travel through those functions, starting with the first half of the iteration. There run_quiz hands the question to ask, ask does all the talking to the player, printing the text and the lettered choices and waiting at input(), and the letter the player types comes straight back to run_quiz as a string.

The second half of the iteration is the judgement. run_quiz passes the question and the typed letter to check, gets back True or False, and updates the score before moving to the next question. Keeping that decision in its own small function is what makes it easy to test later with a few direct calls.

This one file touches every concept from the Fundamentals level, and the table maps each concept to the function where it shows up. Lists and dictionaries hold the questions, the for loop with an accumulator lives in run_quiz, input() and string methods in ask, if/else in check and report, every function returns a value, and every line of output is an f-string. If one of those still feels shaky, that is the function to reread.
| Concept from Fundamentals | Where it appears in the quiz |
|---|---|
| Lists and dictionaries | QUESTIONS, one dict per question |
for loop + accumulate |
run_quiz adding to score |
input() + string methods |
ask reading and cleaning the answer |
if/else |
check, and the report message |
| Functions with return | every function hands a value back |
| f-strings | all output lines |
Code
The whole program in one file. Read it top to bottom once, then follow one question through run_quiz, ask, and check. The stdin block that follows the program stands in for a player who answers the three questions in turn.
# quiz.py
QUESTIONS = [
{
"text": "What does len('abc') return?",
"choices": {"a": "2", "b": "3", "c": "'abc'"},
"answer": "b",
},
{
"text": "Which keyword starts a function?",
"choices": {"a": "func", "b": "define", "c": "def"},
"answer": "c",
},
{
"text": "What is 7 // 2?",
"choices": {"a": "3", "b": "3.5", "c": "4"},
"answer": "a",
},
]
def ask(question):
"""Show one question with its choices and return the cleaned answer."""
print(question["text"])
for letter, choice in question["choices"].items():
print(f" {letter}) {choice}")
return input("Your answer: ").strip().lower()
def check(question, answer):
"""Return True if the answer is correct, and show the right one if not."""
if answer == question["answer"]:
print("Correct!\n")
return True
right = question["choices"][question["answer"]]
print(f"Not quite. The answer was {question['answer']}) {right}\n")
return False
def run_quiz(questions):
"""Ask every question and return the number answered correctly."""
score = 0
for number, question in enumerate(questions, start=1):
print(f"Question {number} of {len(questions)}")
answer = ask(question)
if check(question, answer):
score += 1
return score
def report(score, total):
"""Turn a score into a one-line verdict."""
percent = score / total * 100
if percent == 100:
verdict = "Perfect."
elif percent >= 60:
verdict = "Solid work."
else:
verdict = "Worth another pass through the lessons."
return f"You scored {score}/{total} ({percent:.0f}%). {verdict}"
score = run_quiz(QUESTIONS)
print(report(score, len(QUESTIONS)))
b
B
cQuestion 1 of 3
What does len('abc') return?
a) 2
b) 3
c) 'abc'
Your answer: Correct!
Question 2 of 3
Which keyword starts a function?
a) func
b) define
c) def
Your answer: Not quite. The answer was c) def
Question 3 of 3
What is 7 // 2?
a) 3
b) 3.5
c) 4
Your answer: Not quite. The answer was a) 3
You scored 1/3 (33%). Worth another pass through the lessons.The player typed b, B, and c. Input from a person is messy, so ask normalizes it with .strip().lower() before anything compares it; that single line prevents the most common bug in every quiz ever written, where "B " is marked wrong. Here the capital B was cleaned to b and still judged against question 2's answer, c, so it was wrong on merit, not on formatting. Each function did one thing, and run_quiz never needed to know how a question is printed or compared.
Try it yourself
Extend quiz.py in three steps. First, add two questions of your own to QUESTIONS without touching any function. Second, make ask reject any input that is not one of the choice letters and ask again, using a while loop. Third, add a missed list that collects the text of every wrong question and print it after the report. Run it and answer badly on purpose to see the list.
Common mistakes
- Putting questions inside the functions. Data that lives in the logic is hard to change; keep
QUESTIONSat the top as plain data. - Comparing raw input. Without
.strip().lower(), a trailing space or a capital letter marks a right answer wrong. - One giant block instead of functions. It works today and is unreadable next week; if a function needs a scroll bar, split it.
What's next
- A small program is data at the top plus a handful of functions with one job each.
- Normalize user input at the point it arrives; everything after can trust it.
- You have now used every Fundamentals concept in one working, extendable program.
That completes the Fundamentals level. Next you enter Beginner, starting with the list methods and tricks you need to work with real data: sorting with keys, slicing with steps, and copying without surprises.