Examples
Five small, idiomatic Go programs that cover the patterns you reach for in real projects: a CLI flag parser, a context-aware HTTP client, a graceful HTTP server, a worker pool, and a small TUI table. Each is short and pasteable.
Five idiomatic Go snippets
EXAMPLE
package main
// ============================================================
// 1) CLI with flags + subcommands using flag package
// ============================================================
import (
"context"
"errors"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
func runCLI() {
user := flag.String("user", "", "username (required)")
limit := flag.Int("limit", 20, "result limit")
verbose := flag.Bool("v", false, "verbose output")
flag.Parse()
if *user == "" {
fmt.Fprintln(os.Stderr, "missing -user")
flag.Usage()
os.Exit(2)
}
if *verbose {
fmt.Printf("user=%s limit=%d\n", *user, *limit)
}
}
// ============================================================
// 2) Context-aware HTTP client with timeout + retry
// ============================================================
var httpClient = &http.Client{Timeout: 5 * time.Second}
func fetch(ctx context.Context, url string) ([]byte, error) {
var last error
for attempt := 0; attempt < 3; attempt++ {
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
req.Header.Set("user-agent", "shop-client/1.0")
resp, err := httpClient.Do(req)
if err != nil {
last = err
} else {
defer resp.Body.Close()
if resp.StatusCode/100 == 2 {
return io.ReadAll(resp.Body)
}
last = fmt.Errorf("status %d", resp.StatusCode)
}
select {
case <-time.After(time.Duration(1<<attempt) * 200 * time.Millisecond):
case <-ctx.Done():
return nil, ctx.Err()
}
}
return nil, last
}
// ============================================================
// 3) Graceful HTTP server (SIGTERM-clean shutdown)
// ============================================================
func runServer() {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ok")
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 60 * time.Second,
}
idle := make(chan struct{})
go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
close(idle)
}()
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
panic(err)
}
<-idle
}
// ============================================================
// 4) Worker pool with bounded concurrency
// ============================================================
type Job struct {
URL string
}
type Result struct {
URL string
Err error
N int
}
func crawl(ctx context.Context, jobs []Job, workers int) []Result {
in := make(chan Job)
out := make(chan Result)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := range in {
body, err := fetch(ctx, j.URL)
out <- Result{URL: j.URL, Err: err, N: len(body)}
}
}()
}
go func() {
defer close(in)
for _, j := range jobs {
select {
case in <- j:
case <-ctx.Done():
return
}
}
}()
go func() { wg.Wait(); close(out) }()
var results []Result
for r := range out {
results = append(results, r)
}
return results
}
// ============================================================
// 5) Tiny ASCII table for a CLI
// ============================================================
func printTable(headers []string, rows [][]string) {
widths := make([]int, len(headers))
for i, h := range headers {
widths[i] = len(h)
}
for _, r := range rows {
for i, c := range r {
if len(c) > widths[i] {
widths[i] = len(c)
}
}
}
row := func(parts []string) {
var sb strings.Builder
for i, p := range parts {
sb.WriteString(p)
sb.WriteString(strings.Repeat(" ", widths[i]-len(p)+2))
}
fmt.Println(strings.TrimRight(sb.String(), " "))
}
row(headers)
sep := make([]string, len(headers))
for i := range sep {
sep[i] = strings.Repeat("-", widths[i])
}
row(sep)
for _, r := range rows {
row(r)
}
}
func main() {
if len(os.Args) > 1 && os.Args[1] == "serve" {
runServer()
return
}
printTable(
[]string{"id", "customer", "total"},
[][]string{
{"o1", "alice", "49.95"},
{"o2", "bob", "199.00"},
},
)
}
Why it matters
Always thread a context.Context through any function that does I/O or could block. Even when the caller does not need cancellation today, the moment somebody wraps the call in an HTTP handler or a worker pool, the threaded context turns "leaks goroutines forever" into "cleans up when the request is cancelled".
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…