Go Intermediate #7 Standard Library Tour

7 min read

One of Go’s biggest strengths is its rich standard library. Where older JavaScript ecosystems fill gaps with third-party packages, Go ships rich standard libraries from day one. This post sweeps through the most-used parts at once.

This post is closer to an index — “this package fits this use” — than a deep dive. For detailed usage, each package’s official docs are best.

fmt — formatted I/O #

Already covered in Basics #2, but again.

fmt
fmt.Println("hi")              // automatic newline
fmt.Print("no newline")
fmt.Printf("%s = %d\n", "n", 42)
s := fmt.Sprintf("v=%v", x)    // to string
fmt.Errorf("failed: %w", err)     // create an error

// common verbs
// %v   default
// %+v  with field names
// %#v  in Go syntax
// %d %s %t %f %T %q

strings — string tools #

strings
strings.Contains("hello", "ell")          // true
strings.HasPrefix("hello", "he")           // true
strings.HasSuffix("hello", "lo")           // true
strings.Index("hello", "ll")               // 2
strings.Replace("hello", "l", "L", -1)     // heLLo (-1 means all)
strings.ReplaceAll("hello", "l", "L")      // heLLo
strings.ToUpper("hello")                    // HELLO
strings.ToLower("HELLO")                    // hello
strings.TrimSpace("  hello  ")              // "hello"
strings.Trim("---hi---", "-")               // hi
strings.Split("a,b,c", ",")                 // ["a", "b", "c"]
strings.Join([]string{"a", "b"}, ",")       // a,b
strings.Repeat("ab", 3)                     // ababab
strings.Fields("  a  b  c  ")               // ["a", "b", "c"]

strings.Builder — efficient string assembly #

Builder
var b strings.Builder
b.WriteString("Hello, ")
b.WriteString("World")
result := b.String()    // "Hello, World"

Concatenating with s += other in a loop allocates new memory each time. Builder reuses an internal buffer for far better efficiency.

strconv — string ↔ number #

strconv
strconv.Itoa(42)              // "42"
strconv.Atoi("42")             // (42, nil)
strconv.ParseInt("42", 10, 64) // (42, nil) — base, bit width
strconv.ParseFloat("3.14", 64)
strconv.FormatFloat(3.14, 'f', 2, 64)  // "3.14"
strconv.Quote("hi\n")          // "\"hi\\n\""

In most cases, Itoa / Atoi / ParseFloat is plenty.

bytes[]byte tools #

The byte-slice counterpart of strings, with a nearly identical API.

bytes
bytes.Contains([]byte("hello"), []byte("ll"))
bytes.Equal(a, b)
bytes.Replace(...)

// Buffer — a growable byte buffer
var buf bytes.Buffer
buf.WriteString("hi")
buf.Write([]byte("!"))
fmt.Println(buf.String())

bytes.Buffer satisfies both io.Reader/io.Writer — appears often when reading or writing IO into memory.

io — IO interfaces #

The core interfaces from #1 Interfaces.

io core
type Reader interface { Read(p []byte) (int, error) }
type Writer interface { Write(p []byte) (int, error) }
type Closer interface { Close() error }

io.ReadAll(r)        // read everything to []byte
io.Copy(dst, src)    // copy from src to dst
io.EOF               // end-of-read sentinel
common pattern
data, err := io.ReadAll(resp.Body)
defer resp.Body.Close()

os — OS resources #

os
os.Args                     // command-line arguments
os.Getenv("HOME")
os.Setenv("KEY", "value")
os.Exit(1)                   // terminate the program
os.Stdin / os.Stdout / os.Stderr   // io.Reader/Writer

// files
file, err := os.Open("file.txt")        // read
file, err := os.Create("file.txt")      // write (overwrite)
defer file.Close()

data, err := os.ReadFile("file.txt")     // read all at once
err := os.WriteFile("file.txt", data, 0644)

// directories
entries, err := os.ReadDir("./")
os.MkdirAll("a/b/c", 0755)
os.Remove("file.txt")
os.RemoveAll("dir")

path/filepath — path handling #

filepath
filepath.Join("a", "b", "c")        // a/b/c (OS-aware)
filepath.Base("/a/b/c.go")           // c.go
filepath.Dir("/a/b/c.go")            // /a/b
filepath.Ext("c.go")                  // .go
filepath.Abs("./file")                // absolute path

Handles per-OS separator differences (Linux /, Windows \) automatically.

time — time #

time basics
now := time.Now()
yesterday := now.Add(-24 * time.Hour)
nextHour := now.Add(time.Hour)

// differences
elapsed := time.Since(start)         // elapsed time
duration := time.Until(deadline)      // remaining time

// comparisons
now.After(other)
now.Before(other)
now.Equal(other)

Duration #

Duration
time.Second
time.Minute
time.Hour
time.Millisecond
time.Microsecond
time.Nanosecond

3 * time.Second                    // 3 seconds
500 * time.Millisecond             // 0.5 seconds

Formatting — Go’s distinctive way #

time format
now := time.Now()
fmt.Println(now.Format("2006-01-02 15:04:05"))

// the reference time Go chose
// 2006-01-02 15:04:05.999999999 -0700 MST = Mon Jan 2 15:04:05 MST 2006
// i.e. 1 2 3 4 5 6 7

Unlike other languages that use tokens like YYYY-MM-DD, Go expresses formats by writing a specific reference time (2006-01-02 15:04:05). Awkward at first, intuitive once you’re used to it.

Common constants:

time constants
time.RFC3339         // "2006-01-02T15:04:05Z07:00"
time.RFC1123
time.DateOnly        // "2006-01-02"
time.TimeOnly        // "15:04:05"

Parse #

Parse
t, err := time.Parse(time.RFC3339, "2026-05-04T10:00:00Z")

encoding/json — JSON #

JSON basics
type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Age  int    `json:"age,omitempty"`
}

// serialize
data, err := json.Marshal(user)
data, err := json.MarshalIndent(user, "", "  ")  // pretty

// deserialize
var u User
err := json.Unmarshal(data, &u)

// streaming
encoder := json.NewEncoder(os.Stdout)
encoder.Encode(user)

decoder := json.NewDecoder(file)
err := decoder.Decode(&u)

json: tags — the part we saw in #6 Structs. omitempty says to omit the field from output when it’s the zero value.

Dynamic JSON — map[string]any #

When dealing with JSON of an unknown schema.

dynamic JSON
var data map[string]any
json.Unmarshal(rawJSON, &data)

if name, ok := data["name"].(string); ok {
	fmt.Println(name)
}

Requiring a type assertion on every access gets cumbersome. Defining a struct is cleaner when the schema is known.

sort — sorting #

sort basics
sort.Ints(nums)
sort.Strings(names)
sort.Float64s(values)

// sort a slice — by function
sort.Slice(users, func(i, j int) bool {
	return users[i].Age < users[j].Age
})

// search (in an already-sorted slice)
i := sort.SearchInts(nums, target)

After generics — slices #

The slices package was added in Go 1.21+. A more modern API.

slices (Go 1.21+)
import "slices"

slices.Sort(nums)
slices.SortFunc(users, func(a, b User) int {
	return a.Age - b.Age
})

slices.Contains([]string{"a", "b"}, "a")
slices.Index([]int{10, 20, 30}, 20)         // 1
slices.Reverse(nums)
slices.Min(nums)
slices.Max(nums)

In new code the slices package is becoming increasingly common.

maps — map tools #

maps (Go 1.21+)
import "maps"

maps.Keys(m)        // all keys (iterable; collect to a slice with slices.Collect)
maps.Values(m)
maps.Equal(a, b)
maps.Clone(m)
maps.Delete(m, fn)

errors — error tools #

Detailed in #2 Error Handling.

errors
errors.New("message")
errors.Is(err, target)
errors.As(err, &target)
errors.Join(err1, err2, err3)
errors.Unwrap(err)

net/http — HTTP client and server #

HTTP client
resp, err := http.Get("https://example.com")
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)

// custom client
client := &http.Client{Timeout: 5 * time.Second}
req, _ := http.NewRequestWithContext(ctx, "POST", url, body)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
HTTP server
http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "users")
})
http.ListenAndServe(":8080", nil)

Covered in earnest in the Practice series.

regexp — regular expressions #

regexp
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
match := re.FindStringSubmatch("2026-05-04")
// match: ["2026-05-04", "2026", "05", "04"]

re.MatchString("foo")
re.ReplaceAllString("a1 b2", "X")
re.FindAllString("a1 b2 c3", -1)

bufio — buffered IO #

bufio
scanner := bufio.NewScanner(file)
for scanner.Scan() {
	line := scanner.Text()
	// ...
}
if err := scanner.Err(); err != nil {
	// ...
}

// or Reader
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')

When reading a large file line by line — a memory-safe pattern compared to os.ReadFile.

log and log/slog — logging #

The old log does plain output.

old log package
log.Println("info")
log.Fatalf("fatal: %v", err)   // prints, then os.Exit(1)

Go 1.21+’s log/slogstructured logging (key-value pairs).

slog (Go 1.21+)
import "log/slog"

slog.Info("user logged in", "user_id", "u1", "ip", "127.0.0.1")
slog.Error("DB failed", "err", err)

// JSON output
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)

JSON output makes integration with log analytics tools (Datadog, Grafana, etc.) very easy. In new code, slog is increasingly the standard choice.

flag — command-line flags #

flag
import "flag"

var (
	host = flag.String("host", "localhost", "server address")
	port = flag.Int("port", 8080, "port")
	verbose = flag.Bool("v", false, "verbose")
)

func main() {
	flag.Parse()
	fmt.Println(*host, *port, *verbose)
}
run
./app --host=0.0.0.0 --port=3000 -v

For richer CLI features, reach for external libraries like cobra or kong. The standard flag is enough for small tools.

Other commonly used packages — briefly #

useful packages to know
crypto/rand        secure random
crypto/sha256      hash
encoding/base64
encoding/hex
encoding/csv
encoding/xml
hash/fnv          fast hash (not secure)
math
math/rand          non-secure random
net                low-level networking
net/url            URL parsing
sync               Mutex, WaitGroup, Once, Map
sync/atomic        atomic operations
unicode            unicode tools
unicode/utf8       UTF-8 encoding

Wrap-up #

This post was closer to an index. For detailed usage see each package on pkg.go.dev/std.

Common cases:

  • Strings → strings, fmt, strconv
  • Time → time
  • JSON → encoding/json
  • Sorting → sort or slices (Go 1.21+)
  • HTTP → net/http
  • Logging → log/slog (Go 1.21+)
  • IO → os, io, bufio

Closing the Intermediate series #

What we covered across 7 posts:

  1. Interfaces — implicit implementation (#1)
  2. Error handling — wrap, Is/As, panic (#2)
  3. Goroutines and channels — concurrency intro (#3)
  4. select and timeouts — handling many channels (#4)
  5. context — standard cancellation/timeout tool (#5)
  6. Testing — testing package and table-driven (#6)
  7. Standard library tour — packages you’ll meet often (this post)

With this foundation you can write robust Go code. The next Advanced series goes one step further — concurrency patterns in depth, the memory model, generics, and reflect-level tools.

X