Standard library
The Go standard library covers a substantial portion of routine programming: formatted I/O, string manipulation, file system, networking, HTTP, JSON, time, regular expressions, cryptography, compression, database access (via the SQL driver interface), and more. The library is intentionally broad and stable — packages added to the standard library remain available indefinitely. The conventional Go program leans on the standard library; third-party crates are common (for routing, database ORMs, observability tooling) but the core surface is built in. The combination — substantial breadth, deep stability, single-source documentation at pkg.go.dev — is the substance of Go’s standard-library story.
This tour points out the principal packages and their conventional uses.
fmt
Formatted I/O — the conventional print and format functions:
import "fmt"
fmt.Println("hello", "world") // print with newline
fmt.Printf("%s is %d\n", name, age) // formatted print
fmt.Print("no newline ")
fmt.Sprintln(...) // returns string
fmt.Sprintf(...)
fmt.Sprint(...)
// To io.Writer:
fmt.Fprintln(os.Stderr, "error:", err)
fmt.Fprintf(file, "%d\n", n)
// Reading:
var n int
fmt.Scan(&n) // from stdin
fmt.Sscanf("42", "%d", &n)
The format verbs are treated in Strings.
strings and strconv
strings for manipulation; strconv for numeric conversions:
import (
"strings"
"strconv"
)
strings.Contains("hello", "ll")
strings.HasPrefix("hello", "he")
strings.Split("a,b,c", ",")
strings.Join([]string{"a", "b"}, ",")
strings.ToUpper("hello")
strings.TrimSpace(" hello ")
strings.Replace("hello", "l", "L", -1) // -1 = all
strconv.Itoa(42)
strconv.Atoi("42")
strconv.FormatFloat(3.14, 'f', 2, 64)
strconv.ParseFloat("3.14", 64)
Treated in Strings.
time
Time and duration:
import "time"
now := time.Now()
fmt.Println(now)
fmt.Println(now.Format(time.RFC3339)) // "2026-05-07T15:04:05Z"
fmt.Println(now.Format("2006-01-02 15:04:05")) // Go's reference time
t, err := time.Parse(time.RFC3339, "2026-01-15T10:00:00Z")
t, err := time.Parse("2006-01-02", "2026-01-15")
d := 5 * time.Second
d := 100 * time.Millisecond
d := time.Hour + 30*time.Minute
later := now.Add(d)
diff := later.Sub(now) // Duration
time.Sleep(2 * time.Second)
start := time.Now()
doWork()
elapsed := time.Since(start)
The reference time is Mon Jan 2 15:04:05 MST 2006 — the format strings spell out fields using these exact values. The convention is unique to Go.
For durations:
time.Nanosecond
time.Microsecond
time.Millisecond
time.Second
time.Minute
time.Hour
// Methods:
d.Seconds() // float64
d.Milliseconds() // int64
d.Hours()
For tickers and timers:
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for t := range ticker.C {
fmt.Println(t)
}
timer := time.NewTimer(5 * time.Second)
<-timer.C // blocks for 5 seconds
os
Operating-system interaction:
import "os"
os.Args // []string
os.Getenv("HOME")
os.Setenv("KEY", "value")
os.Environ() // []string ("KEY=VALUE" pairs)
dir, err := os.Getwd() // current directory
err := os.Chdir("/tmp")
err := os.Mkdir("dir", 0755)
err := os.MkdirAll("a/b/c", 0755)
err := os.Remove("file.txt")
err := os.RemoveAll("dir")
f, err := os.Open("file.txt")
f, err := os.Create("output.txt")
defer f.Close()
info, err := os.Stat("file.txt")
fmt.Println(info.Size(), info.ModTime())
os.Exit(1) // exit with status
// stdin/stdout/stderr:
os.Stdin // *os.File
os.Stdout
os.Stderr
path/filepath
Cross-platform path manipulation:
import "path/filepath"
filepath.Join("a", "b", "c") // "a/b/c" or "a\\b\\c"
filepath.Base("/etc/hosts") // "hosts"
filepath.Dir("/etc/hosts") // "/etc"
filepath.Ext("file.txt") // ".txt"
filepath.Abs("./relative") // absolute path
filepath.Clean("a/./b/../c") // "a/c"
matches, err := filepath.Glob("*.go") // glob matches
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
fmt.Println(path)
return nil
})
// Or with the Go 1.16+ fs.WalkDir for better performance:
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
return nil
})
For URL-style paths (always /-separated), use path instead of path/filepath.
io
The principal I/O interfaces:
import "io"
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Combined interfaces:
io.ReadWriter
io.ReadCloser
io.WriteCloser
io.ReadWriteCloser
io.Seeker
Conventional functions:
io.Copy(dst, src) // copy until EOF
io.CopyN(dst, src, n) // copy n bytes
io.ReadAll(r) // []byte (Go 1.16+)
io.ReadFull(r, buf) // fill buf or error
io.LimitReader(r, n) // bounded reader
io.MultiReader(r1, r2, r3) // concatenated readers
io.MultiWriter(w1, w2) // tee writer
io.TeeReader(r, w) // reads from r and writes to w
io.EOF // sentinel error for end of stream
Treated in I/O.
bufio
Buffered I/O:
import "bufio"
reader := bufio.NewReader(os.Stdin)
line, err := reader.ReadString('\n')
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
process(line)
}
writer := bufio.NewWriter(file)
writer.WriteString("hello\n")
writer.Flush()
Treated in I/O.
encoding/json
JSON serialisation and deserialisation:
import "encoding/json"
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
// Marshal:
data, err := json.Marshal(p)
data, err := json.MarshalIndent(p, "", " ")
// Unmarshal:
var p Person
err := json.Unmarshal(data, &p)
// Streaming:
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
encoder.Encode(p)
decoder := json.NewDecoder(file)
err := decoder.Decode(&p)
// Generic JSON:
var m map[string]interface{}
err := json.Unmarshal(data, &m)
Other encoding packages: encoding/xml, encoding/csv, encoding/base64, encoding/hex, encoding/binary.
net/http
HTTP client and server:
import "net/http"
// Client:
resp, err := http.Get("https://example.com")
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
resp, err := http.Post(url, "application/json", strings.NewReader(body))
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("POST", url, body)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
// Server:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, world!")
})
http.HandleFunc("/api/", apiHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
// With a custom router (since Go 1.22):
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser)
mux.HandleFunc("POST /users", createUser)
log.Fatal(http.ListenAndServe(":8080", mux))
The Go 1.22 net/http router admits substantial path matching with method specifiers and path variables.
net
Lower-level networking:
import "net"
// TCP:
conn, err := net.Dial("tcp", "example.com:80")
listener, err := net.Listen("tcp", ":8080")
for {
conn, err := listener.Accept()
go handle(conn)
}
// UDP:
addr, err := net.ResolveUDPAddr("udp", ":8080")
conn, err := net.ListenUDP("udp", addr)
// DNS:
ips, err := net.LookupIP("example.com")
mxs, err := net.LookupMX("example.com")
context
Cancellation and deadline propagation:
import "context"
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ctx, cancel := context.WithCancel(parent)
ctx := context.WithValue(parent, key, value)
select {
case <-ctx.Done():
return ctx.Err()
case result := <-doWork(ctx):
return result, nil
}
Treated in Concurrency.
log and log/slog
Logging:
import "log"
log.Println("info message")
log.Printf("count: %d", n)
log.Fatal("fatal — exits with status 1")
log.Panic("logs and panics")
// Custom logger:
logger := log.New(os.Stderr, "PREFIX ", log.LstdFlags|log.Lshortfile)
logger.Println("hello")
Since Go 1.21, log/slog provides structured logging:
import "log/slog"
slog.Info("user logged in", "user", username, "ip", ip)
slog.Error("operation failed", "error", err, "id", id)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("structured", "key", "value")
The slog package is the conventional contemporary Go logger.
regexp
Regular expressions:
import "regexp"
re := regexp.MustCompile(`^[a-zA-Z]+$`)
re.MatchString("hello") // true
re.FindString("hello world") // "hello"
re.FindAllString("a1b2c3", -1) // ["a", "b", "c"]
re.ReplaceAllString("a1b2", "X") // "X1X2"
re := regexp.MustCompile(`(\w+) (\w+)`)
matches := re.FindStringSubmatch("hello world") // ["hello world", "hello", "world"]
Go’s regexp uses RE2 syntax — guarantees linear time but does not support backreferences.
sort and slices
Sorting:
import "sort"
s := []int{3, 1, 4}
sort.Ints(s)
sort.Strings(strs)
sort.Sort(sort.Reverse(sort.IntSlice(s)))
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
idx := sort.Search(len(s), func(i int) bool { return s[i] >= target })
Since Go 1.21, the slices package provides generic sorting:
import "slices"
slices.Sort(s)
slices.SortFunc(people, func(a, b Person) int {
return cmp.Compare(a.Age, b.Age)
})
slices.IsSorted(s)
slices.BinarySearch(s, target)
database/sql
Database access:
import (
"database/sql"
_ "github.com/lib/pq" // driver registration
)
db, err := sql.Open("postgres", connStr)
defer db.Close()
err := db.Ping()
rows, err := db.Query("SELECT id, name FROM users WHERE active = $1", true)
defer rows.Close()
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil { /* ... */ }
fmt.Println(id, name)
}
err := db.QueryRow("SELECT name FROM users WHERE id = $1", id).Scan(&name)
result, err := db.Exec("UPDATE users SET active = $1 WHERE id = $2", true, id)
n, err := result.RowsAffected()
// Transactions:
tx, err := db.Begin()
defer tx.Rollback()
tx.Exec(...)
tx.Commit()
The package admits a driver-independent interface; the database driver (PostgreSQL, MySQL, SQLite) is imported separately.
crypto/...
Cryptography:
import (
"crypto/sha256"
"crypto/hmac"
"crypto/rand"
"crypto/aes"
"crypto/tls"
)
h := sha256.Sum256([]byte("hello")) // [32]byte
fmt.Printf("%x\n", h)
mac := hmac.New(sha256.New, key)
mac.Write(message)
sig := mac.Sum(nil)
buf := make([]byte, 16)
rand.Read(buf) // cryptographically secure random
encoding/...
Encoding utilities:
import (
"encoding/json"
"encoding/xml"
"encoding/csv"
"encoding/base64"
"encoding/hex"
"encoding/binary"
)
base64.StdEncoding.EncodeToString(data)
base64.StdEncoding.DecodeString(s)
hex.EncodeToString(data)
binary.BigEndian.Uint32(buf[0:4])
binary.LittleEndian.PutUint32(buf, value)
compress/...
Compression:
import (
"compress/gzip"
"compress/zlib"
)
w := gzip.NewWriter(file)
w.Write(data)
w.Close()
r, _ := gzip.NewReader(file)
defer r.Close()
data, _ := io.ReadAll(r)
errors
Error utilities:
import "errors"
err := errors.New("something failed")
errors.Is(err, target)
errors.As(err, &targetType)
errors.Unwrap(err)
errors.Join(err1, err2, err3) // Go 1.20+
Treated in Error handling.
flag
Command-line argument parsing:
import "flag"
var (
name = flag.String("name", "world", "name to greet")
age = flag.Int("age", 0, "age")
verbose = flag.Bool("v", false, "verbose")
)
flag.Parse()
fmt.Printf("Hello, %s (%d)\n", *name, *age)
For more elaborate CLIs, third-party crates like cobra and urfave/cli are conventional.
testing
Test framework:
import "testing"
func TestAdd(t *testing.T) {
if got := Add(2, 3); got != 5 {
t.Errorf("Add(2, 3) = %d, want 5", got)
}
}
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
func ExampleAdd() {
fmt.Println(Add(2, 3))
// Output: 5
}
// Table-driven tests:
func TestParse(t *testing.T) {
tests := []struct {
in string
want int
err bool
}{
{"42", 42, false},
{"abc", 0, true},
}
for _, tc := range tests {
t.Run(tc.in, func(t *testing.T) {
got, err := Parse(tc.in)
if (err != nil) != tc.err {
t.Errorf("err = %v", err)
}
if got != tc.want {
t.Errorf("got %d, want %d", got, tc.want)
}
})
}
}
Run with go test, go test -v (verbose), go test -race (race detection), go test -cover (coverage).
runtime
Runtime information:
import "runtime"
runtime.GOMAXPROCS(0) // number of OS threads
runtime.NumCPU() // CPU cores
runtime.NumGoroutine() // active goroutines
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
runtime.GC() // force GC
runtime.Gosched() // yield to other goroutines
sync and sync/atomic
Synchronisation primitives — treated in Concurrency.
A note on the conventional discipline
The Go standard library is the conventional starting point. Reach for third-party crates when:
- Web routing —
gorilla/mux,chi,gin,echo. - Database ORM —
gorm,sqlx,sqlc,ent. - Configuration —
viper,koanf. - CLI —
cobra,urfave/cli. - Observability —
opentelemetry,prometheus. - gRPC —
google.golang.org/grpc. - Async work queues —
asynq,machinery.
The conventional Go ecosystem leans heavily on the standard library; many production applications use only standard packages plus a handful of third-party crates.
A note on the conventional discipline
The standard-library advice:
- Reach for the standard library first — it covers most needs.
- Use
fmt,strings,strconvfreely — they are the foundation. - Use
timefor time and duration; the reference format is unique. - Use
osandpath/filepathfor OS interaction. - Use
ioandbufio— theReader/Writerinterfaces are foundational. - Use
encoding/jsonfor JSON;database/sqlfor SQL. - Use
net/http— the substantial built-in HTTP support is one of Go’s strengths. - Use
log/slog(Go 1.21+) for structured logging. - Use
slicesandmaps(Go 1.21+) for the conventional generic operations. - Use
testing— the standard testing framework is conventional.
The combination — substantial breadth, deep stability, the pkg.go.dev documentation, the go doc command, the conventional cmd/ and internal/ layout — is the substance of Go’s standard library philosophy. The discipline produces self-contained programs with substantial functionality.