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
- Parse messy text with strings + strconv
- Aggregate records into a map
- Order results deterministically with sort
- Format an aligned report with fmt
- Recall iota, runes, embedding, and regexp
- Combine many packages into one pipeline
📚 Section Recap
A quick map of everything this section covered — skim it, and revisit any lesson where a row feels shaky.
| Topic | Key idea |
|---|---|
| iota | Counter that numbers const blocks; 1 << iota builds bit flags |
| strings | Immutable bytes; Split/Join/Replace; strings.Builder |
| runes | byte=uint8, rune=int32; len counts bytes; range yields runes |
| fmt | Verbs %d %s %v %+v %T %q; Stringer; Errorf %w |
| conversions | Explicit T(v); strconv; comma-ok assertions; type switch |
| embedding | Promoted fields/methods; composition; overriding |
| slices | ptr/len/cap header; append grows; copy; aliasing |
| maps | Set with struct{}; comma-ok; sort keys for order |
| sorting | sort.Slice, SliceStable, slices.SortFunc |
| time | time.Date; Duration math; reference layout 2006-01-02 |
| regexp | MustCompile; 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)
- ❌ Atoi failed on a field with spaces — input like " 75". ✅ strings.TrimSpace before converting.
- ❌ Report order changes each run — you printed straight from a map. ✅ Collect keys, sort, then iterate.
- ❌ One bad line crashed the whole parse — unhandled error. ✅ Check err and continue.
- ❌ Columns don't line up — no width verbs. ✅ Use %-6s and %3d to pad.
🎯 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!
- ✅ You combined strings, strconv, maps, sort, and fmt into one pipeline
- ✅ Parsed messy input robustly with TrimSpace and error checks
- ✅ Produced deterministic output by sorting keys before printing
- ✅ Reviewed iota, runes, embedding, slices, time, and regexp
- ✅ Passed the checkpoint quiz on the whole standard-library section
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
- Previous: Regular Expressions (regexp)
- Next: select & Multiplexing Channels — Wait on multiple channels with select, default, and timeouts with time.After
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.