In this project you build a small quiz that runs entirely in the terminal. The
program stores its questions in a slice of structs, asks each one in turn, reads
the player’s typed answer from standard input, and prints a final score such as
You scored 2 out of 3.. It is a complete, buildable Go program you can run with
a single go run ..
Skills you practise
- Modelling data with a
structand grouping records in a slice. - Reading interactive input line by line with
bufio.Scanneronos.Stdin. - Writing small, testable helper functions.
- Comparing strings robustly (case-insensitive, trimmed) to grade answers.
Prerequisites
You should be comfortable with the basics before starting:
- Functions in Go — you will factor the grading logic into its own function.
- Arrays and slices in Go — the questions live in a slice.
- Error handling in Go — you will guard against a closed input stream.
Final file tree
quiz-cli/
├── go.mod
└── main.go
Steps
1. Initialise the module and model a question
Create the folder, initialise a module, then describe what a question is. Each question has a prompt and the answer we accept as correct.
package main
type Question struct {
Prompt string
Answer string
}
Run go mod init quizcli in the folder so the code becomes a buildable module.
2. Write a helper to grade an answer
A dedicated function keeps main short and, crucially, makes the grading logic
easy to test. We accept the answer regardless of case and ignore stray spaces.
import "strings"
// isCorrect reports whether the given answer matches the expected one,
// ignoring case and surrounding spaces.
func isCorrect(q Question, given string) bool {
return strings.EqualFold(strings.TrimSpace(given), q.Answer)
}
3. Read the player’s answers with bufio.Scanner
bufio.Scanner reads standard input one line at a time. Scan returns false
when the stream is closed (for example on end-of-file), which lets us stop
cleanly instead of crashing.
scanner := bufio.NewScanner(os.Stdin)
for i, q := range questions {
fmt.Printf("Question %d/%d: %s\n> ", i+1, len(questions), q.Prompt)
if !scanner.Scan() {
break
}
// grade scanner.Text() here
}
4. Assemble the complete program
Put the pieces together: a slice of questions, a running score, and a summary at the end. This program compiles and runs as-is.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// Question holds a prompt and the answer we accept as correct.
type Question struct {
Prompt string
Answer string
}
// isCorrect reports whether the given answer matches the expected one,
// ignoring case and surrounding spaces.
func isCorrect(q Question, given string) bool {
return strings.EqualFold(strings.TrimSpace(given), q.Answer)
}
func main() {
questions := []Question{
{Prompt: "What keyword declares a function in Go?", Answer: "func"},
{Prompt: "Which command compiles and runs a program?", Answer: "go run"},
{Prompt: "What is the zero value of a bool?", Answer: "false"},
}
scanner := bufio.NewScanner(os.Stdin)
score := 0
for i, q := range questions {
fmt.Printf("Question %d/%d: %s\n> ", i+1, len(questions), q.Prompt)
if !scanner.Scan() {
break
}
if isCorrect(q, scanner.Text()) {
fmt.Println("Correct!")
score++
} else {
fmt.Printf("Wrong. Expected: %s\n", q.Answer)
}
}
fmt.Printf("\nYou scored %d out of %d.\n", score, len(questions))
}
Run it with go run . and answer each prompt.
Acceptance criteria
-
go run .starts the quiz and prints one prompt at a time. - A correct answer increments the score; a wrong one shows the expected value.
- Answers are matched ignoring case and surrounding whitespace.
- The program prints a final
You scored X out of Y.line. - Pressing Ctrl-D (end of input) stops the quiz without a panic.
Extensions
- Shuffle the questions with
math/randso each run differs. - Load the questions from a JSON or CSV file instead of hard-coding them.
- Add a timer per question and reward faster answers.
- Support multiple-choice questions by printing options and reading a letter.
Tests
Because grading lives in isCorrect, you can test it without any input/output.
Add a main_test.go file and run go test ./....
package main
import "testing"
func TestIsCorrect(t *testing.T) {
q := Question{Prompt: "keyword?", Answer: "func"}
cases := []struct {
name string
given string
want bool
}{
{"exact", "func", true},
{"trimmed spaces", " func ", true},
{"different case", "FUNC", true},
{"wrong answer", "def", false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := isCorrect(q, c.given); got != c.want {
t.Errorf("isCorrect(%q) = %v, want %v", c.given, got, c.want)
}
})
}
}
Conclusion
You have built a working, testable terminal quiz and practised structs, slices, input handling and a table-driven test. A natural next step is to persist data between runs: continue with the task manager CLI project, where you save state to a JSON file.