A Beginner's Introduction to Golang with a Simple Quiz Game

"Go was conceived while waiting for a C++ program to compile"

- A half true joke

This blog comprises of two parts. The first part introduces you to Golang while the second part tells you how to get your hands dirty with Golang through a simple Golang tutorial. You can directly skip to it.

What is Go?

Go, also known as Golang, is a programming language developed by Google and publicly launched in 2009. The inspiration behind it was to address some of the challenges faced by their engineers when working with massive codebases. Out of discontent with using existing old languages like C++ and Java for Google's large scale of operations, Go was designed to simplify the development process, making it easier for programmers to write, read, and maintain code.

Unlike traditional object-oriented languages, Go doesn’t have classes; it focuses on interfaces but at the same time you don’t have to implement the declaration.

Who is using Go?

🔵 Google (Duh)

🔵 Twitter

🔵 Twitch

🔵 Riot Games

🔵 Meta (Facebook)

🔵 Microsoft

🔵 Netflix

🔵 Uber

... and many others.

Why Go?

The founding principles of Go were to create a language that:

  • Improves Developer Productivity: Go seems simple to code in, kind of like Python, but you don’t have to deal with brace placement or indentation thus making it easier for code reviews.
  • Facilitates Scalable Engineering Efforts: Features like the Go Format tool (gofmt) automatically format Go code according to a standard style.
  • Supports Concurrent Programming: Go offers features like goroutines for concurrency, which makes it possible to perform multiple tasks simultaneously without the complex threading models present in other languages.
  • Enhances Code Reliability and Efficiency: Go's design encourages the use of static typing and garbage collection, which improves performance and reliability. The language's simplicity also means fewer bugs and more straightforward maintenance.
  • Streamlines Dependency Management: Tools like Go Imports automate the management of package imports, simplifying dependency management. This reduces the friction of working with codebases and external libraries, making development faster and more enjoyable.

To summarize, it is a good choice if you want development speed and simplicity. It’s also a good choice for web development if you look for concurrency and performance as key factors for your project, because Go’s performance is better than Node JS (Go is compiled into machine language unlike NodeJS which is an interpreted language).

Is Go relevant in 2024?

Go seems to be back on the charts after a looong time, so I guess it's safe to say yes, it is becoming relevant.

usage

github

Charts Source

Let’s Go!

OK, let’s get started with the tutorial. We are going to make a very simple quiz application, by having a CSV file with sample questions and answers and getting the answer right scores 1 point.

Setup

First go to the official site and download the release appropriate for your OS. In my case I am using Windows x64 (Intel) so I will be downloading https://go.dev/dl/go1.21.6.windows-amd64.msi

download

Once you are done installing, make a new directory and create two files: quiz.csv and quiz.go. Open your directory in your favourite IDE (Mine is VSCode).

Inside quiz.csv add the following data: (You can get creative and add more columns or questions and answers as you want, for the user to select. eg: Category - Science, etc.)

"What is the capital of France?", "Paris"
"Who wrote 'To Kill a Mockingbird'?", "Harper Lee"
"Which country is the largest in the world?", "Russia"
"What is the only food that can never go bad?", "Honey"
"How many moons does Neptune have?", "14"
"What produces the majority of the breathable air on earth?", "Oceans"
"How many Academy Awards did Titanic win?", "11"
"What color is a giraffe's tongue?", "Purple"
"What are baby rabbits called?", "Kits"
"What animal breathes through its butt?", "Turtle"

Inside quiz.go start adding the following code. You will find the entire code at the end of the tutorial.

First, we will import all the packages we need.

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"os"
	"strings"
)
  • bufio: Used for buffered I/O operations, here we are going to use this for reading user answer input from the console.
  • encoding/csv: For reading and writing CSV (Comma Separated Values) files.
  • fmt: Implements formatted I/O operations, similar to C's printf and scanf.
  • os: Provides a platform-independent interface to operating system functionality, including file handling.
  • strings: For manipulating UTF-8 encoded strings.
💡

Note that we are using bufio instead of fmt.Scanln to read user input because in the answer for the second question there is a space ("Harper Lee"). fmt.Scanln reads only "Harper" as the input for the current question, because it stops reading at the first space or newline character. On the other hand, bufio.Scanner, particularly with its default split function bufio.ScanLines, treats a line of input as a single entity, regardless of the spaces within it. It reads the entire line of input until a newline character is encountered.

Next inside the main function we are going to open the quiz.csv file and store the file in the ‘file’ variable. defer file.Close() schedules the file.Close() operation to run at the end of the main function, ensuring the file is closed properly and resources are released. If there is an error, we are not going to continue. The nil identifier denotes a zero value. Here is an interesting read on it if you like: nils in Go.

func main() {
	// Open the CSV file
	file, err := os.Open("quiz.csv")
	if err != nil {
		fmt.Println("Error opening CSV file:", err)
		return
	}
	defer file.Close()
}

Then, we are going to read this file by initializing a new CSV reader which reads all records (lines) from the CSV file into lines, a slice of string slices. Each slice in lines represents a line from the file, with each element in the slice representing a cell in the CSV.

func main() {
	// ... previous code

	// Parse the file
	r := csv.NewReader(file)
	lines, err := r.ReadAll()
	if err != nil {
		fmt.Println("Error reading CSV lines:", err)
		return
	}
}

Now comes the main logic of our quiz app.

func main() {
	// ... previous code
	// Create a new scanner to read input from the standard input
	scanner := bufio.NewScanner(os.Stdin)
	// Process the quiz
	correct := 0
	for i, line := range lines {
		question, answer := line[0], strings.TrimSpace(line[1])
		fmt.Printf("Question %d: %s\n", i+1, question)
		fmt.Print("Your answer: ")

		// Use the scanner to read the next line of input
		scanner.Scan()
		userAnswer := scanner.Text()

		if strings.EqualFold(strings.TrimSpace(userAnswer), answer) {
			correct++
		}
	}
}

After initializing the scanner to read user input and the counter correct to track the number of correct answers, Then iterates over each line in the CSV file. i is the index (starting from 0), and line is the value (a slice of strings representing the cells in the line).

Each line is split into question and answer - we added the question first followed by the answer in the csv file -, while trimming any leading or trailing whitespace from the answer for user friendly user inputs, by comparing with the user’s input answer later.

The next four statements are pretty straightforward; we print the question and prompt the user for the answer. Finally, we do the answer comparison by trimming the user’s answer through the strings.EqualFold which returns true if the strings are equal and it ignores the case (case insensitive comparison).

We will handle scan errors outside the loop for efficiency by avoiding checking for scan errors after every single call to scanner.Scan(). If an error occurs during scanning, scanner.Scan() will return false, but it doesn't immediately report this error. Instead, the scanner stores the error internally, which we retrieve later by using scanner.Err().

Finally, we print the scores.

func main() {
	// ... previous code
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}

	fmt.Printf("You scored %d out of %d.\n", correct, len(lines))
}

That’s all! Here is the entire code:

package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"os"
	"strings"
)

func main() {
	// Open the CSV file
	file, err := os.Open("quiz.csv")
	if err != nil {
		fmt.Println("Error opening CSV file:", err)
		return
	}
	defer file.Close()

	// Parse the file
	r := csv.NewReader(file)
	lines, err := r.ReadAll()
	if err != nil {
		fmt.Println("Error reading CSV lines:", err)
		return
	}

	// Create a new scanner to read input from the standard input
	scanner := bufio.NewScanner(os.Stdin)
	// Process the quiz
	correct := 0
	for i, line := range lines {
		question, answer := line[0], strings.TrimSpace(line[1])
		fmt.Printf("Question %d: %s\n", i+1, question)
		fmt.Print("Your answer: ")

		// Use the scanner to read the next line of input
		scanner.Scan()
		userAnswer := scanner.Text()

		if strings.EqualFold(strings.TrimSpace(userAnswer), answer) {
			correct++
		}
	}

	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}

	fmt.Printf("You scored %d out of %d.\n", correct, len(lines))
}

Run the app by using the command:

go run quiz.go

Result:

result

References:

Golang interview

J. Meyerson, "The Go Programming Language," in IEEE Software, vol. 31, no. 5, pp. 104-104, Sept.-Oct. 2014, doi: 10.1109/MS.2014.127.