The Standard Library You Will Actually Use
Go's standard library is unusually complete, and leaning on it is idiomatic rather than lazy. This lesson covers the packages that turn up in almost every program.
encoding/json
package main
import (
"encoding/json"
"fmt"
"strings"
)
type Course struct {
Slug string `json:"slug"`
Title string `json:"title"`
Lessons int `json:"lessons"`
Tags []string `json:"tags,omitempty"`
Published bool `json:"-"` // never serialised
}
func main() {
course := Course{Slug: "go", Title: "Go", Lessons: 12, Published: true}
encoded, err := json.Marshal(course)
if err != nil {
panic(err)
}
fmt.Println(string(encoded))
var decoded Course
if err := json.Unmarshal(encoded, &decoded); err != nil {
panic(err)
}
fmt.Printf("%+v\n", decoded)
// Unknown fields are ignored by default; DisallowUnknownFields is how you
// catch a typo in a config file instead of silently using the zero value.
strict := json.NewDecoder(strings.NewReader(`{"slug":"go","lessonz":12}`))
strict.DisallowUnknownFields()
var target Course
fmt.Println("strict decode:", strict.Decode(&target))
// Decoding into a map when the shape is not known ahead of time.
var loose map[string]any
_ = json.Unmarshal([]byte(`{"a":1,"b":["x","y"]}`), &loose)
fmt.Printf("%T for a number, %T for an array\n", loose["a"], loose["b"])
}
Note the types in that last line: every JSON number decodes into float64 and
every array into []any. That is the price of map[string]any, and the reason
a struct is almost always better.
time
package main
import (
"fmt"
"time"
)
func main() {
// A fixed instant, so this lesson prints the same thing every run.
launch := time.Date(2026, time.March, 14, 9, 30, 0, 0, time.UTC)
fmt.Println(launch.Format(time.RFC3339))
fmt.Println(launch.Format("Mon 2 Jan 2006 at 15:04")) // the reference layout
later := launch.Add(90 * time.Minute)
fmt.Println(later.Sub(launch), later.After(launch))
deadline := launch.AddDate(0, 1, 0)
fmt.Println("a month later:", deadline.Format(time.DateOnly))
parsed, err := time.Parse(time.RFC3339, "2026-03-14T09:30:00Z")
fmt.Println(parsed.Equal(launch), err)
// Durations are typed, which stops the "was that seconds or milliseconds?"
// class of bug entirely.
timeout := 250 * time.Millisecond
fmt.Println(timeout, timeout.Seconds())
start := time.Now()
time.Sleep(10 * time.Millisecond)
fmt.Println("elapsed at least 10ms:", time.Since(start) >= 10*time.Millisecond)
}
That layout string is Go's one real oddity: the reference time is
Mon Jan 2 15:04:05 MST 2006 - 01/02 03:04:05PM '06 -0700 - and you write the
format by example rather than with %Y.
strings, strconv and bytes
package main
import (
"fmt"
"strconv"
"strings"
)
func main() {
s := " Go: From Basics to Expert "
fmt.Printf("%q\n", strings.TrimSpace(s))
fmt.Println(strings.Contains(s, "Basics"), strings.HasPrefix(strings.TrimSpace(s), "Go"))
fmt.Println(strings.Split("a,b,c", ","))
fmt.Println(strings.Join([]string{"a", "b", "c"}, " -> "))
fmt.Println(strings.ReplaceAll("one one one", "one", "1"))
fmt.Println(strings.Fields(" several spaced words "))
fmt.Println(strings.EqualFold("GoLang", "golang")) // ASCII-insensitive
// strconv converts; it never guesses.
n, err := strconv.Atoi("42")
fmt.Println(n, err)
_, err = strconv.Atoi("42x")
fmt.Println("error:", err)
fmt.Println(strconv.Itoa(42) + "!")
fmt.Println(strconv.FormatFloat(3.14159, 'f', 2, 64))
fmt.Println(strconv.Quote("needs \"escaping\""))
b, _ := strconv.ParseBool("true")
fmt.Println(b)
}
io, and why it composes
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func main() {
// Anything that reads is an io.Reader; anything that writes is an io.Writer.
// A file, a socket, a buffer and a string all are, so code written against
// them works with all four.
reader := strings.NewReader("first line\nsecond line\nthird line\n")
scanner := bufio.NewScanner(reader)
count := 0
for scanner.Scan() {
count++
fmt.Printf(" %d: %s\n", count, scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(err)
}
// io.Copy moves bytes from any reader to any writer without a buffer of
// your own - here from a string straight to stdout.
if _, err := io.Copy(os.Stdout, strings.NewReader("copied directly\n")); err != nil {
panic(err)
}
// A writer that counts, in six lines, usable anywhere an io.Writer is.
counting := &countingWriter{}
fmt.Fprintf(io.MultiWriter(os.Stdout, counting), "written to two places\n")
fmt.Println("bytes counted:", counting.n)
}
type countingWriter struct{ n int }
func (w *countingWriter) Write(p []byte) (int, error) {
w.n += len(p)
return len(p), nil
}
sort, slices and maps
package main
import (
"fmt"
"maps"
"slices"
"strings"
)
type Learner struct {
Name string
Score int
}
func main() {
learners := []Learner{{"Ada", 95}, {"Grace", 88}, {"Alan", 95}}
// slices.SortFunc (Go 1.21+) replaces most uses of the old sort package.
slices.SortFunc(learners, func(a, b Learner) int {
if c := b.Score - a.Score; c != 0 {
return c // descending by score
}
return strings.Compare(a.Name, b.Name)
})
fmt.Println(learners)
numbers := []int{5, 2, 9, 1}
slices.Sort(numbers)
fmt.Println(numbers, slices.Contains(numbers, 9), slices.Index(numbers, 5))
fmt.Println(slices.Max(numbers), slices.Min(numbers))
reversed := slices.Clone(numbers) // Clone, so the original is untouched
slices.Reverse(reversed)
fmt.Println(reversed, numbers)
stock := map[string]int{"pears": 2, "apples": 5, "cherries": 9}
keys := slices.Sorted(maps.Keys(stock)) // map iteration is random; sort it
fmt.Println(keys)
// maps.Keys and maps.Values return iterators (Go 1.23+), so they are ranged
// over directly rather than returning a slice.
total := 0
for v := range maps.Values(stock) {
total += v
}
fmt.Println("total", total)
}
What to take away
encoding/jsonreads struct tags, and only exported fields; unknown fields are ignored unless you forbid them.- A JSON number in a
map[string]anyis afloat64- prefer a struct. - Go's time layouts are written by example, using the reference
2006-01-02 15:04:05. time.Durationis a type, which removes a whole class of unit bug.io.Reader/io.Writercompose:bufio,io.Copyandio.MultiWriterwork with anything.slicesandmaps(Go 1.21+) replace most hand-written sorting and key extraction.