Build a command-line quiz in Go

Build a command-line quiz in Go: define questions with structs, read answers from stdin with bufio.Scanner, then score them and print the result.

Estimated time
~4 h
Skills
CLI, functions, error-handling, slices
On this page

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 struct and grouping records in a slice.
  • Reading interactive input line by line with bufio.Scanner on os.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:

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/rand so 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.

Search

Search runs entirely in your browser.