Checkpoint: Standard Library

Reviewed & published by Brayan K

This checkpoint consolidates the standard-library section — iota, strings, runes, fmt, conversions, embedding, slices, maps, sorting, time, and regexp — into one build challenge plus a quiz that proves you can combine them.

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'll Practice in This Checkpoint

📚 Section Recap

A quick map of everything this section covered — skim it, and revisit any lesson where a row feels shaky.

TopicKey idea
iotaCounter that numbers const blocks; 1 << iota builds bit flags
stringsImmutable bytes; Split/Join/Replace; strings.Builder
runesbyte=uint8, rune=int32; len counts bytes; range yields runes
fmtVerbs %d %s %v %+v %T %q; Stringer; Errorf %w
conversionsExplicit T(v); strconv; comma-ok assertions; type switch
embeddingPromoted fields/methods; composition; overriding
slicesptr/len/cap header; append grows; copy; aliasing
mapsSet with struct{}; comma-ok; sort keys for order
sortingsort.Slice, SliceStable, slices.SortFunc
timetime.Date; Duration math; reference layout 2006-01-02
regexpMustCompile; Find/Submatch; named groups; ReplaceAll

🛠️ Build Challenge: Score Leaderboard

Turn a block of messy name,score text into a ranked leaderboard. This is a complete mini-pipeline: parse with strings + strconv, aggregate the best score per name into a map, sort deterministically (score descending, then name), and report an aligned table with fmt.

package main

import (
    "fmt"
    "sort"
    "strconv"
    "strings"
)

// A record is one "name,score" line.
type Record struct {
    Name  string
    Score int
}

func main() {
    // Raw input: lines of "name,score". Some have stray whitespace.
    raw := `ada,90
ben, 75
cara,88
ben,82
ada,95`

    // 🎯 BUILD CHALLENGE — fill in the four steps:
    // 1) PARSE  : strings.Split into lines, then split each on ",",
    //             TrimSpace both fields, strconv.Atoi the score.
    // 2) AGGREGATE: keep the BEST score per name in a map[string]int.
    // 3) SORT   : sort names by score DESC, breaking ties by name ASC.
    // 4) REPORT : print "rank. name score" aligned with %-6s and %3d.
    //
    // ✅ Expected output:
    // === Leaderboard ===
    // 1. ada     95
    // 2. cara    88
    // 3. ben     82

    _ = raw
    _ = sort.Slice
    _ = strconv.Atoi
    _ = strings.Split
    // your code here
}

A complete, compiling solution. Each numbered comment maps to a recap topic — note how TrimSpace rescues the ben, 75 line and the map keeps only each name's best score.

📝 Checkpoint Quiz

Answer each before you reveal it. If you miss one, the recap row above links straight back to the lesson.

6. len counts bytes, and the é is 2 bytes in UTF-8, so a 5-character string is 6 bytes. Use utf8.RuneCountInString for the character count (5).

A sub-slice shares the parent's backing array and inherits its capacity. If append stays within that capacity it writes in place, overwriting later parent elements. Use copy or the full-slice expression s[low:high:max] to avoid it.

Use the comma-ok form: v, ok := m[k]. ok is true only when the key exists; a plain lookup returns the zero value for both "absent" and "present with value 0".

sort.SliceStable (and slices.SortStableFunc). Plain sort.Slice is not guaranteed stable, so ties can be reordered.

"2006-01-02". Go uses the reference date Mon Jan 2 15:04:05 MST 2006; you spell the output with that exact date's pieces, not letter codes like YYYY.

Compiling is relatively expensive and the pattern is usually constant. Doing it once at startup (a package-level var) avoids recompiling in hot loops, and an invalid pattern panics immediately rather than mid-request. The compiled *Regexp is safe for concurrent use.

Common Errors (and the fix)

🎯 Mini-Challenge: Extend the Report

Once your leaderboard works, extend it: after the table, print the average of the best scores formatted to one decimal (e.g. avg: 88.3) using %.1f. Sum the best values and divide by len(best) as a float64.

This reuses conversions (float64(...)), maps, and fmt precision — the whole section in one extra line.

🎉 Checkpoint Complete!

Practice quiz

What does len("héllo") return in Go, and why?

  • 5, because there are 5 characters
  • 5, because Go counts runes
  • 6, because len counts bytes and é is 2 bytes in UTF-8
  • 7, including a null terminator

Answer: 6, because len counts bytes and é is 2 bytes in UTF-8. len counts bytes; é is 2 bytes in UTF-8, so a 5-character string is 6 bytes. Use utf8.RuneCountInString for 5.

Why does the leaderboard call strings.TrimSpace on each field before strconv.Atoi?

  • Because input like " 75" has stray whitespace that Atoi would reject
  • To convert the field to uppercase
  • Because Atoi only accepts trimmed strings of length 2
  • To remove commas from the number

Answer: Because input like " 75" has stray whitespace that Atoi would reject. strconv.Atoi rejects leading/trailing spaces, so TrimSpace makes parsing robust against messy input like 'ben, 75'.

How do you tell a missing map key apart from a key stored with a zero value?

  • Compare the value to nil
  • Call len(m[k])
  • There is no way to tell them apart
  • Use the comma-ok form: v, ok := m[k]

Answer: Use the comma-ok form: v, ok := m[k]. The comma-ok form returns ok=true only when the key exists; a plain lookup gives the zero value for both cases.

In the build challenge, why are the names collected into a slice and sorted before printing?

  • Because maps cannot be ranged over
  • Because map iteration order is randomized, so sorting makes the output deterministic
  • To remove duplicate names
  • Because fmt.Printf needs a slice

Answer: Because map iteration order is randomized, so sorting makes the output deterministic. Go randomizes map iteration order; collecting keys into a slice and sorting them produces stable, testable output.

Which sort function keeps equal elements in their original relative order?

  • sort.SliceStable
  • sort.Slice
  • sort.Ints
  • sort.Reverse

Answer: sort.SliceStable. sort.SliceStable (and slices.SortStableFunc) is stable; plain sort.Slice may reorder ties.

What layout string formats a time as 2026-06-18 with time.Format?

  • "YYYY-MM-DD"
  • "01-02-2006"
  • "2006-01-02"
  • "2021-12-31"

Answer: "2006-01-02". Go uses the reference date Mon Jan 2 15:04:05 MST 2006, so the layout is the literal "2006-01-02".

What is the difference between a byte and a rune in Go?

  • byte is int32 and rune is uint8
  • byte is an alias for uint8; rune is an alias for int32 (a Unicode code point)
  • They are identical aliases
  • byte is for ASCII only; rune cannot hold ASCII

Answer: byte is an alias for uint8; rune is an alias for int32 (a Unicode code point). byte = uint8 (a raw byte), rune = int32 (a Unicode code point); ranging a string yields runes.

Why compile a regular expression with regexp.MustCompile at package level rather than inside a hot loop?

  • MustCompile is the only way to compile a regex
  • Package-level regexes match faster than local ones
  • It makes the regex case-insensitive
  • Compiling is relatively expensive, so doing it once avoids recompiling, and an invalid pattern panics immediately at startup

Answer: Compiling is relatively expensive, so doing it once avoids recompiling, and an invalid pattern panics immediately at startup. Compile once at startup: it avoids repeated cost in hot paths, fails fast on a bad pattern, and the *Regexp is safe for concurrent use.

Why might appending to a sub-slice unexpectedly change the parent slice?

  • append always copies, so it never affects the parent
  • A sub-slice shares the parent's backing array, and append within capacity writes in place
  • Sub-slices are pointers to the parent variable
  • Because Go re-sorts slices after append

Answer: A sub-slice shares the parent's backing array, and append within capacity writes in place. If append stays within the shared backing array's capacity it overwrites parent elements; use copy or s[low:high:max] to avoid it.

What does the format verb %-6s do in fmt.Printf?

  • Prints the string right-aligned in 6 columns
  • Truncates the string to 6 characters
  • Prints the string left-aligned, padded to a minimum width of 6
  • Prints 6 copies of the string

Answer: Prints the string left-aligned, padded to a minimum width of 6. The minus flag left-justifies and 6 sets a minimum field width, padding shorter strings with spaces to align columns.

Continue this course

Frequently asked questions

What does this checkpoint cover?

It ties together the standard-library skills from this section: constants and iota, strings and the strings package, runes/bytes/UTF-8, formatting with fmt, type conversions and assertions, struct embedding, slice internals, advanced maps, sorting, time and dates, and regular expressions. The build challenge uses several of these at once — parsing with strings and strconv, aggregating with a map, ordering with sort, and reporting with fmt.

Why split parsing, aggregating, sorting, and reporting into separate steps?

Separating the stages keeps each one simple and testable. Parsing turns messy text into clean structs; aggregation reduces many records to one value per key with a map; sorting imposes a deterministic order; and reporting formats the result. This pipeline shape — parse, transform, order, present — shows up constantly in real Go programs, from log processors to report generators.

How do I make map-based output deterministic?

Map iteration order is randomized, so you never print straight from a range over a map when order matters. Instead, collect the keys into a slice, sort that slice with a less func (here: score descending, then name ascending), and iterate the sorted slice. That guarantees the same output every run, which is also what makes the expected output testable.

Why TrimSpace each field while parsing?

Real input is rarely perfectly clean — note the line 'ben, 75' has a leading space before the number. strconv.Atoi would reject ' 75' with a parse error, so trimming whitespace from each field before converting makes the parser robust. Guarding the conversion with an error check (continue on failure) also keeps one bad line from crashing the whole program.

Where do I go after this checkpoint?

Next is select and multiplexing channels, which builds on Go's concurrency model. After mastering the standard-library toolkit here, you'll combine it with goroutines and channels to write concurrent programs — for example fanning out parsing work across goroutines and collecting results through channels with select.

Related lessons