The Standard Library Tour
Go ships with an unusually complete standard library: a large set of packages included with every Go installation, covering string manipulation, file and network I/O, JSON encoding, HTTP clients and servers, cryptography, concurrency primitives, and a built-in testing framework. Where many languages lean on third-party frameworks for basics like a web server or a test runner, Go bundles production-quality tools for these directly into the language distribution. Learning to navigate the standard library – knowing what exists, how to read its documentation, and which package solves which problem – is one of the highest-leverage skills a Go programmer can build, because it means writing less code while trusting more of it.
Overview: How the Standard Library Works
Every Go installation includes the source code for its standard library alongside the compiler itself. Each package corresponds to a directory of .go files that share a single package declaration, and a package’s import path mirrors its location: the package you import as "strings" lives in a directory named strings, the package imported as "encoding/json" lives in encoding/json, and so on. Because the source is right there on disk, you can always read the real implementation of a standard-library function – there is no hidden magic, just Go code compiled the same way your own code is.
Two tools make the library easy to explore without leaving the terminal or your editor: running go doc fmt.Println prints the documentation comment and signature for a specific symbol, and the website pkg.go.dev hosts searchable, always-up-to-date documentation for every package in the standard library (plus most public third-party modules). Before writing a helper function by hand, it is almost always worth a quick search – chances are the standard library, or occasionally a package one level away like slices or maps, already does what you need.
The compiler enforces discipline around imports: every package you import must be used somewhere in the file, and every declared local variable must be used too. This is not a style suggestion, it is a compile error – go build refuses to produce a binary if it finds an unused import or variable. That strictness keeps dependency lists honest: if a file imports "time", you know for certain the file actually uses the time package somewhere.
The standard library is organized loosely by concern rather than forced into a single object hierarchy – there is no base class every type must extend. The table below groups the packages you will reach for constantly.
| Package | Purpose |
|---|---|
fmt |
Formatted printing and scanning (Println, Printf, Sprintf, Errorf) |
strings / strconv |
String manipulation, and conversions between strings and numbers/booleans |
os / io / bufio |
Files, command-line args, environment variables, and buffered streaming I/O |
time |
Dates, durations, timers, and formatting/parsing timestamps |
encoding/json |
Marshaling Go values to JSON and unmarshaling JSON into Go values |
net/http |
A full HTTP client and a production-ready HTTP server |
errors |
Creating, wrapping, and inspecting error values |
sync / context |
Low-level concurrency primitives and cancellation/deadlines |
slices / maps |
Generic helpers for common slice and map operations (Go 1.21+) |
testing |
Go’s built-in unit testing and benchmarking framework |
Go 1.21 also folded several long-requested conveniences directly into the language and library: the builtins min, max, and clear work on any ordered type or map/slice without an import, and the new slices and maps packages provide generic, well-tested implementations of operations like sorting, searching, and comparing that programmers previously wrote by hand for every element type.
Syntax
You bring a standard-library package into scope with an import statement, using its import path as a string literal. A single file can import one package at a time or group several inside parentheses.
import (
"fmt"
"strings"
myjson "encoding/json"
_ "net/http/pprof"
)
| Form | Meaning |
|---|---|
import "fmt" |
Import a single package, referenced by its default name (fmt) |
import ( "a" "b" ) |
Grouped import block, one path per line – the idiomatic form for multiple imports |
myjson "encoding/json" |
Import under a custom local name; refer to the package as myjson.Marshal, etc. |
_ "net/http/pprof" |
Blank import – runs the package’s init functions for side effects only; you cannot reference its names |
Examples
Example 1: Parsing numbers with strings and strconv
package main
import (
"fmt"
"strconv"
"strings"
)
func main() {
raw := " 42,17,89,3 "
trimmed := strings.TrimSpace(raw)
parts := strings.Split(trimmed, ",")
sum := 0
for _, p := range parts {
n, err := strconv.Atoi(strings.TrimSpace(p))
if err != nil {
fmt.Println("invalid number:", p)
continue
}
sum += n
}
fmt.Println("sum:", sum)
}
Output:
sum: 151
This combines three of the most commonly used packages in Go. strings.TrimSpace removes leading and trailing whitespace, strings.Split breaks a string into a slice on a separator, and strconv.Atoi (“ASCII to integer”) converts each piece into an int, returning an error instead of panicking if the text is not a valid number. Handling that error explicitly – rather than assuming every field parses cleanly – is what makes the loop safe to run on messy, real-world input.
Example 2: Sorting and dates with slices and time
package main
import (
"fmt"
"slices"
"time"
)
func main() {
nums := []int{5, 2, 9, 1, 7}
slices.Sort(nums)
fmt.Println("sorted:", nums)
start := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2026, time.August, 7, 0, 0, 0, 0, time.UTC)
duration := end.Sub(start)
fmt.Printf("days elapsed: %.0f\n", duration.Hours()/24)
}
Output:
sorted: [1 2 5 7 9]
days elapsed: 218
slices.Sort is a generic function from the slices package (Go 1.21+) that sorts any ordered slice in place, replacing the older pattern of calling sort.Ints or writing a custom sort.Interface. The time package models an instant with time.Time and a span with time.Duration; subtracting two Time values with Sub yields a Duration, and Hours() converts it to a floating-point number of hours, which is divided by 24 to get whole days.
Example 3: Structured data with encoding/json and errors
package main
import (
"encoding/json"
"errors"
"fmt"
)
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Year int `json:"year"`
}
func parseBook(data []byte) (Book, error) {
var b Book
if err := json.Unmarshal(data, &b); err != nil {
return Book{}, fmt.Errorf("parsing book: %w", err)
}
if b.Title == "" {
return Book{}, errors.New("book title is required")
}
return b, nil
}
func main() {
input := `{"title":"The Go Programming Language","author":"Alan Donovan and Brian Kernighan","year":2015}`
book, err := parseBook([]byte(input))
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("%s by %s (%d)\n", book.Title, book.Author, book.Year)
out, err := json.MarshalIndent(book, "", " ")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(string(out))
}
Output:
The Go Programming Language by Alan Donovan and Brian Kernighan (2015)
{
"title": "The Go Programming Language",
"author": "Alan Donovan and Brian Kernighan",
"year": 2015
}
Struct tags like `json:"title"` tell encoding/json which JSON key maps to which Go field; without a tag, the package falls back to the field’s name. json.Unmarshal decodes bytes into the struct, json.MarshalIndent encodes it back out with readable indentation, and fmt.Errorf with the %w verb wraps an underlying error so callers can still inspect it with errors.Is or errors.As instead of losing the original cause.
How It Works Step by Step
Walking through Example 3 end to end: main builds a raw JSON string as a byte slice and passes it to parseBook. Inside parseBook, json.Unmarshal uses reflection to inspect the Book struct’s fields and their json tags, then walks the JSON text key by key, copying each matching value into the corresponding field of b. If the JSON is malformed, Unmarshal returns a non-nil error immediately, which parseBook wraps and returns without ever reaching the validation check. If decoding succeeds, parseBook runs one extra manual check – that Title isn’t empty – because encoding/json only guarantees the JSON parsed correctly, not that the data makes business sense. Back in main, the returned error is checked before the book is used; only then does execution reach the Printf call and the MarshalIndent call, which performs the same reflection-based walk in reverse, turning the struct back into indented JSON text.
Common Mistakes
Mistake 1: Ignoring the error a standard-library call returns
n, _ := strconv.Atoi(userInput)
fmt.Println(n * 2)
If userInput is not a valid number, Atoi returns 0 along with a non-nil error. Discarding the error with _ turns bad input into a silently wrong answer (0) instead of surfacing the problem where it happened.
n, err := strconv.Atoi(userInput)
if err != nil {
fmt.Println("invalid input:", err)
return
}
fmt.Println(n * 2)
Mistake 2: Writing to a nil map
var counts map[string]int
counts["go"] = 1 // panic: assignment to entry in nil map
A nil map has no underlying hash table allocated. Reading from it is safe and returns the zero value for missing keys, but writing to it requires an allocated table, so the runtime panics instead of allocating one for you.
counts := make(map[string]int)
counts["go"] = 1 // works fine
Mistake 3: Comparing time.Time values with ==
if t1 == t2 {
fmt.Println("same time")
}
A time.Time value can carry an internal monotonic clock reading alongside the wall-clock time. Two values representing the exact same instant can compare as unequal with == if one lost its monotonic reading (for example, after being decoded from a string or a database), and == also fails to recognize the same instant expressed in two different time zones as equal.
if t1.Equal(t2) {
fmt.Println("same time")
}
Best Practices
- Search
go docor pkg.go.dev before writing a helper by hand – the standard library already solves most common problems, from string building to CSV parsing to hashing. - Let
gofmt/goimportsformat and group your imports automatically rather than ordering them by hand. - Wrap errors with
fmt.Errorf("...: %w", err)so callers can useerrors.Is/errors.Asinstead of comparing error strings. - Use
strings.Builderinstead of repeated+=concatenation in a loop – it avoids allocating a new string on every iteration. - Reach for the generic
slicesandmapspackages (Go 1.21+) for operations like sorting, searching, and equality instead of hand-rolled loops. - Always
make()a map before writing to it, and comparetime.Timevalues with.Equal, never==. - Pass a
context.Contextas the first parameter to functions that do I/O or might run long, so callers can cancel or set a deadline.
Practice Exercises
- Write a program that takes a slice of email address strings, uses
strings.Splitto extract the domain after the@from each one, and prints the domains sorted alphabetically withslices.Sort. - Using the
timepackage, write a function that accepts a birth date as atime.Timeand returns the person’s age in whole years as of today. Hint: compare month and day, not just the year, to handle birthdays that haven’t occurred yet this year. - Define a struct
ProductwithName,Price, andInStockfields and appropriatejsontags. Write a program that unmarshals a JSON array of products into[]Productand prints each product’s name and price withfmt.Printf.
Summary
- The standard library ships with every Go installation – no external dependencies are needed for strings, JSON, HTTP, time, or testing.
- A package’s import path mirrors its directory name;
go docand pkg.go.dev are the fastest ways to discover what is available. fmt,strings,strconv, anderrorsform the everyday toolkit;encoding/jsonandnet/httpcover most services;syncandcontexthandle concurrency.- Go 1.21 added the generic
slicesandmapspackages plus themin,max, andclearbuiltins, reducing the need for hand-written helper loops. - Always check returned errors, initialize maps with
makebefore writing to them, and comparetime.Timevalues with.Equal, not==. - Leaning on the standard library instead of reinventing it keeps Go programs small, fast to compile, and easy to audit.
