Go’s evolution is intentionally slow. By 1.24 in 2026, generics have matured, range-over-func brings real iterators, swiss tables make maps faster, and slog is the standard logging story. This post is the working summary.
Range-over-func — proper iterators
Before:
for i, v := range slice { ... }
for k, v := range mp { ... }
Now (1.23+, mature in 1.24):
func (db *DB) Iter() iter.Seq2[string, []byte] {
return func(yield func(string, []byte) bool) {
rows, _ := db.Query("SELECT k, v FROM kv")
defer rows.Close()
for rows.Next() {
var k string; var v []byte
rows.Scan(&k, &v)
if !yield(k, v) { return }
}
}
}
for k, v := range db.Iter() {
fmt.Println(k, string(v))
}
Lazy, composable iteration. Replaces channel-based fakery from before generics.
Generics in everyday Go
func MapKeys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
The standard library’s slices, maps, cmp packages give you slice / map / comparator helpers without rolling your own.
Swiss tables
The runtime now uses Swiss-table-based maps. Free 10–20% perf on map-heavy workloads. No code changes needed.
Weak pointers (1.24)
import "weak"
w := weak.Make(&heavyObject)
// ...later
if obj := w.Value(); obj != nil {
use(obj)
}
Useful for caches that shouldn’t pin big objects in memory. Niche but powerful.
structured logging — slog
import "log/slog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
slog.Info("user created", "id", user.ID, "email", user.Email)
slog.Error("failed to process", "err", err, "request_id", reqID)
slog is the new standard. Structured, leveled, JSON-friendly, performant. Replace log everywhere.
For tracing-correlated logs see OpenTelemetry End-to-End in 2026 .
errors.Is/errors.As patterns
err := db.Get(ctx, id)
if errors.Is(err, ErrNotFound) {
return notFoundResponse()
}
var dbErr *sqlx.DBError
if errors.As(err, &dbErr) && dbErr.Code == "23505" {
return conflictResponse()
}
The 2026 way to handle errors. Combine with sentinel errors and typed wrappers.
cmp package for comparators
slices.SortFunc(orders, func(a, b Order) int {
return cmp.Compare(a.Total, b.Total)
})
Cleaner than the old Less func interface.
Context patterns
Always pass ctx context.Context as the first argument:
func (s *Service) GetUser(ctx context.Context, id int64) (*User, error) {
return s.db.QueryRowContext(ctx, "SELECT ...", id).Scan(...)
}
Deadline propagation, cancellation, request-scoped values all flow through context. Don’t store contexts in structs.
errgroup for parallel calls
g, ctx := errgroup.WithContext(ctx)
var user User; var posts []Post
g.Go(func() error {
var err error
user, err = s.GetUser(ctx, id)
return err
})
g.Go(func() error {
var err error
posts, err = s.GetPosts(ctx, id)
return err
})
if err := g.Wait(); err != nil {
return err
}
Run two concurrent calls; first error cancels the group. The Go answer to async parallelism.
Build / runtime knobs
GOMAXPROCS=4 ./app # bound parallelism
GOMEMLIMIT=512MiB ./app # GC target
GODEBUG=gctrace=1 ./app # GC traces
For containerized runs, set GOMAXPROCS to your CPU limit and GOMEMLIMIT to your memory limit. The runtime will behave better under cgroup pressure.
Profiling
import _ "net/http/pprof"
// then run pprof at /debug/pprof
Free profiling endpoint. Production-safe behind auth.
Read this next
- Production HTTP Service with net/http
- Go + gRPC + Protocol Buffers
- Go Web Frameworks: Gin, Echo, Chi
- Go Concurrency: Goroutines and Channels
If you want a Go 1.24 service template (slog, ctx, errgroup, pgx, otel), it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .