Checkpoint: Go Basics

Reviewed & published by Brayan K

You've learned data types, control flow, loops, arrays, slices, maps and pointers — let's combine them. This checkpoint has no new syntax. Instead you'll prove the pieces fit together by building one realistic program that reads a slice of structs, tallies with a map, and loops with range — then test yourself with a short quiz.

Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You've Learned So Far

Everything in this checkpoint draws on the seven lessons before it. Here's the toolkit you're now carrying:

TopicKey ideaYou can now…
Data types & iotaSized numbers, runes, enumsconst ( A = iota )
Control flowBranch with if / switchif x > 0 { } switch { }
for & rangeGo's one loop, any collectionfor i, v := range s
ArraysFixed length, value semantics[3]int{1,2,3}
SlicesGrowable, shared backings = append(s, x)
MapsKey/value, comma-ok, tallycounts[k]++
PointersShare & mutate without copyingp := &x; *p = 9

🛠️ Build Challenge: Order Summary

One small program ties together a struct, a slice, a map, a range loop, integer-to-float conversion, and key sorting. Start from the scaffold below. The data and the Item struct are given — your job is the loop in the middle and the printing at the end.

Your starting point

Fill in the loop and the output. Work through the four numbered steps in the comments — attempt it before peeking.

package main

import (
    "fmt"
    "sort"
)

// 🎯 BUILD CHALLENGE: Order summary
// A small shop has these line items. Your job, step by step:
//   1. Define an Item struct with Name (string), Price (int, in pence)
//      and Qty (int).
//   2. Put several Items in a SLICE.
//   3. RANGE over the slice to:
//        - add each line's (Price * Qty) to a running grandTotal
//        - TALLY how many units of each Name into a map[string]int
//   4. Print the grand total in pounds, then each product's unit
//      count in ALPHABETICAL order (sort the keys!).
//
// Expected output:
//   Grand total: £5.49
//   apple x4
//   bread x1
//   milk x2

type Item struct {
    Name  string
    Price int // pence
    Qty   int
}

func main() {
    items := []Item{
        {Name: "apple", Price: 50, Qty: 4},  // 200p
        {Name: "milk", Price: 99, Qty: 2},    // 198p
        {Name: "bread", Price: 151, Qty: 1},  // 151p
    }

    grandTotal := 0
    counts := map[string]int{}

    // your code here:
    //  - loop with range over items
    //  - grandTotal += it.Price * it.Qty
    //  - counts[it.Name] += it.Qty

    // then print the total in pounds and the sorted counts
    _ = sort.Strings // hint: you'll need sort.Strings on the keys
    _ = grandTotal
    _ = counts
    fmt.Println("replace me")
}

Stuck? Reveal the worked solution

The whole thing fits in one pass over the slice. Notice three deliberate choices: a single range loop does both the money total and the tally; float64(grandTotal)/100 avoids integer division so the pounds print correctly; and the map keys are copied into a slice and sort.Strings'd because map order is random.

package main

import (
    "fmt"
    "sort"
)

type Item struct {
    Name  string
    Price int // pence
    Qty   int
}

func main() {
    items := []Item{
        {Name: "apple", Price: 50, Qty: 4},
        {Name: "milk", Price: 99, Qty: 2},
        {Name: "bread", Price: 151, Qty: 1},
    }

    grandTotal := 0
    counts := map[string]int{}

    // Step 3: one pass over the slice does BOTH jobs.
    for _, it := range items {
        grandTotal += it.Price * it.Qty // running money total (pence)
        counts[it.Name] += it.Qty       // tally units per product name
    }

    // Step 4a: grand total in pounds. float64() avoids integer division.
    fmt.Printf("Grand total: £%.2f\n", float64(grandTotal)/100)

    // Step 4b: maps iterate in RANDOM order, so sort the keys first.
    names := make([]string, 0, len(counts))
    for name := range counts {
        names = append(names, name)
    }
    sort.Strings(names)

    for _, name := range names {
        fmt.Printf("%s x%d\n", name, counts[name])
    }
}

📝 Checkpoint Quiz

Answer each in your head, then reveal it. Aim for at least 5 out of 6 before moving on.

1. What does counts[k]++ do when k isn't yet in the map?

It reads the missing key as the zero value 0, adds 1, and stores 1. No need to check for the key first — this is the standard tally idiom.

2. Why is 7 / 2 equal to 3 in Go, and how do you get 3.5?

Both operands are int, so Go does integer division and truncates. Convert first: float64(7) / 2 gives 3.5.

3. After b := a; b[0] = 9, when does a[0] change — for an array vs a slice?

For an array, a is unchanged (arrays are copied). For a slice, a[0] becomes 9 too, because both slices share the same backing array.

4. Why must you write s = append(s, x) and not just append(s, x)?

append may allocate a new backing array and returns the resulting slice. If you drop the assignment, your new element can be lost.

5. How do you make a function change the caller's variable?

Take a pointer parameter (func f(n *int)), call it with the address (f(&x)), and write through it (*n = ...). Passing a plain value only edits a copy.

6. Why might two runs of a program print map entries in a different order?

Go deliberately randomizes map iteration order so code never relies on it. For stable output, collect the keys into a slice and sort them first.

Frequently Asked Questions

Q: I couldn't finish the build challenge — should I move on?

Revisit the specific lesson the sticking point came from (slices, maps, or pointers) and try again. Checkpoints are diagnostic: a gap here is exactly what they're meant to surface before it compounds.

Q: Do I need to memorise sort.Strings?

No — just remember the pattern: collect keys into a slice, sort the slice, then range it. You can always look up the exact function name in the docs.

Q: What's next after this checkpoint?

Interfaces — Go's way of describing behaviour without inheritance. They build directly on the struct and method ideas you'll see in the very next lesson on methods.

🎉 Checkpoint Complete!

Practice quiz

What does counts[k]++ do when the key k is not yet present in the map?

  • It panics with a nil map error
  • It does nothing because the key is absent
  • It reads the missing key as 0, adds 1, and stores 1
  • It returns false

Answer: It reads the missing key as 0, adds 1, and stores 1. A missing key reads as the value type's zero value (0 for int), so counts[k]++ stores 1 — the standard tally idiom.

In Go, what is the result of the integer expression 7 / 2?

  • 3
  • 3.5
  • 4
  • a compile error

Answer: 3. Both operands are int, so Go performs integer division and truncates toward zero, giving 3.

How do you get 3.5 from dividing 7 by 2 in Go?

  • Use 7 // 2
  • Add a decimal: 7.0 is not allowed, so it cannot be done
  • Use math.Div(7, 2)
  • Convert first: float64(7) / 2

Answer: Convert first: float64(7) / 2. Converting an operand to float64 makes the division floating-point, yielding 3.5.

After b := a; b[0] = 9, when does a[0] also become 9?

  • When a is an array
  • When a is a slice (both share the same backing array)
  • Never — assignment always copies
  • Only for maps

Answer: When a is a slice (both share the same backing array). Arrays are copied on assignment, but a slice header copy still points at the same backing array, so the change is shared.

Why must you write s = append(s, x) rather than just append(s, x)?

  • append may allocate a new backing array and returns the resulting slice, which you must capture
  • append is not a real function
  • Go forbids ignoring return values
  • It prevents the element from being garbage collected

Answer: append may allocate a new backing array and returns the resulting slice, which you must capture. append can reallocate and returns the (possibly new) slice; dropping the assignment can lose your appended element.

How do you make a function modify the caller's variable?

  • Return the new value and ignore it
  • Declare the variable as global
  • Pass a pointer (func f(n *int)), call f(&x), and write through it with *n = ...
  • Mark the parameter with the mutable keyword

Answer: Pass a pointer (func f(n *int)), call f(&x), and write through it with *n = .... Passing a pointer and writing through *n edits the caller's variable; passing a value only edits a local copy.

Why might two runs of the same program print map entries in different orders?

  • Maps store keys by insertion time, which varies
  • Go deliberately randomizes map iteration order so code never relies on it
  • The garbage collector reshuffles maps
  • It is undefined behavior and a bug

Answer: Go deliberately randomizes map iteration order so code never relies on it. Go randomizes map iteration order on purpose; collect keys into a slice and sort them for stable output.

Using the comma-ok form v, ok := m[k], what is ok?

  • true only if the value is non-zero
  • always true for a non-nil map
  • the number of entries in the map
  • true only if the key exists in the map

Answer: true only if the key exists in the map. ok reports whether the key is present, distinguishing a missing key from a stored zero value.

What is the difference between an array and a slice in Go?

  • Arrays grow automatically; slices have fixed length
  • An array has a fixed length that is part of its type; a slice is a growable view over a backing array
  • They are identical aliases
  • Slices can only hold strings

Answer: An array has a fixed length that is part of its type; a slice is a growable view over a backing array. [3]int and [4]int are distinct array types with fixed length; a slice []int is a dynamic, growable view.

Which loop keyword does Go provide for iterating over slices and maps?

  • foreach
  • while
  • for ... range
  • iterate

Answer: for ... range. Go has a single loop keyword, for, and the for ... range form iterates collections.

Continue this course