Skip to content

Topic archive

Go

Go, also known as Golang, is a statically typed programming language designed at Google for simplicity, fast compilation, efficient execution, and practical concurrency. It is widely used for backend services, cloud tooling, networking software, and command-line applications.

179 articles
Go 07 Sep 2026 12 min read

Read Large Line-Oriented Input Safely in Go with bufio.Scanner

Line-oriented input looks simple until one record is much larger than expected. A program may process thousands of ordinary log lines correctly, then stop on a generated stack trace, a large JSON record, or a malformed input that contains no newline for megabytes. Go’s bufio.Scanner is convenient for this job because it handles tokenization and defaults to scanning lines. But that convenience comes with an important boundary: a scanner has a maximum token size. If a token cannot fit within that limit, scanning stops with an error.

Go 07 Sep 2026 11 min read

Read File Regions Safely in Go with io.ReaderAt

Many file formats are not consumed strictly from beginning to end. An index may point to records at known offsets. A binary container may keep metadata in a header and payloads elsewhere. A server may need to serve several independent byte ranges from the same open file. The obvious approach is to call Seek, then Read. That works when one goroutine owns the file position. It becomes fragile when several operations share the same file because the current offset is mutable state.

Go 07 Sep 2026 14 min read

Observe Streaming Reads in Go with io.TeeReader

Streaming code often needs to do two things with the same bytes. A service may need to parse a response while computing its checksum. A file importer may want to decode records while recording exactly what the decoder consumed. A diagnostic tool may want to inspect a stream without first loading the entire input into memory. A common but wasteful approach is to read everything into a byte slice and then run each operation over that copy. That is simple for small inputs, but it removes the main advantage of streaming: work can begin before the whole input has arrived, and memory usage does not have to grow with the full input size.

Go 07 Sep 2026 9 min read

Handle Go HTTP Response Bodies Without Leaking Connections

An HTTP request can appear to work while quietly making later requests more expensive. A common cause in Go clients is mishandling http.Response.Body: forgetting to close it, returning before cleanup is arranged, or assuming that an HTTP error status is returned as a Go error. The important mental model is that a successful Client.Do call gives your code ownership of a stream, not a byte slice. The response headers have arrived, but the body is consumed as you read it. Your code must decide how much of that stream it needs and must close it when finished.

Go 07 Sep 2026 8 min read

Handle Buffered Write Errors Correctly in Go

Buffering output can reduce write overhead, but it also changes when an I/O failure becomes visible. With an unbuffered writer, a call to Write normally reaches the underlying destination immediately. With bufio.Writer, a successful call may only mean that the bytes were accepted into memory. The actual write to a file, socket, pipe, or other destination can happen later, often during Flush. That distinction creates a common production bug: code checks every apparent write, defers Flush, and still returns success after the final flush fails.

Go 07 Sep 2026 12 min read

Compose Sequential Streams in Go with io.MultiReader

Programs often need to present several pieces of data as one input stream. A request body may need a generated header followed by a file. A test may need a prefix, a fixture, and a suffix. A protocol adapter may need to expose several existing readers through an API that accepts only one io.Reader. The obvious solution is to read every piece into memory, concatenate the byte slices, and create a new reader over the result. That is reasonable for small, already-buffered data. It is a poor fit when one source is large, slow, or naturally streaming because the consumer cannot start until the combined buffer has been built.

Go 07 Sep 2026 7 min read

Combine Independent Failures in Go with errors.Join

A function sometimes performs several independent operations and more than one can fail. Cleanup is a common example: closing one resource should not prevent the program from attempting to close the next. Validation can have the same shape when callers benefit from seeing several independent problems at once. Returning only the last error loses information. Returning only the first error may hide failures that happened later. Go’s errors.Join provides a standard way to return one error value that still wraps multiple underlying errors.

Go 06 Sep 2026 9 min read

Handle Long Lines Safely in Go with bufio.Scanner

bufio.Scanner is one of the simplest ways to process line-oriented input in Go. It is a good fit for log files, command output, newline-delimited JSON, and other formats where one logical record fits in memory. The convenience has an important boundary: a Scanner will stop if the next token grows beyond the amount of buffering it is allowed to use. That default protects a program from growing memory without bound, but it can surprise code that works on small test files and later encounters one unusually long line in production.

Go 05 Sep 2026 8 min read

Bound Untrusted Stream Reads in Go with io.LimitReader

Reading an entire io.Reader is convenient, but convenience can become a resource problem when the reader is not fully under your control. A request body, uploaded file, decompressed stream, subprocess output, or protocol payload may be much larger than expected. Calling io.ReadAll directly asks Go to keep reading until EOF, growing memory as needed. The important mental model is that a size limit should sit in front of the consumer. Instead of trusting every caller to stop at the right point, wrap the source in a reader that exposes only a bounded prefix. Go’s io.LimitReader does exactly that.

Go 04 Sep 2026 9 min read

Understand Escape Analysis and Heap Allocations in Go

A Go program can create many values without you choosing whether each value lives on a goroutine stack or in heap memory. The compiler usually makes that decision for you. This becomes important when a hot path allocates more than expected. A small helper function may look harmless, yet a value it creates can outlive the function call and require heap storage. More heap allocation can mean more work for the garbage collector, but changing code blindly to avoid the heap can make a program harder to understand without producing a measurable benefit.

Go 04 Sep 2026 10 min read

Coordinate Shared State with sync.Cond in Go

A goroutine sometimes cannot make progress until shared state changes. A worker may need to wait until a queue contains an item. A producer may need to wait until that queue has free capacity. Several goroutines may need to sleep until a service becomes ready. Polling the state in a loop wastes CPU or forces you to invent arbitrary sleep intervals. Channels solve many coordination problems more directly, but they are not always a natural fit when several goroutines already share state protected by a mutex and need to wait for predicates over that state.

Go 03 Sep 2026 11 min read

Use sync.Pool for Temporary Object Reuse in Go

Repeatedly allocating short-lived helper objects can become expensive in a hot path. A formatter may create temporary buffers for every request, an encoder may allocate scratch space for every record, or a parser may repeatedly construct helper objects that are discarded immediately after use. Go’s sync.Pool can reuse some of those temporary objects across independent operations. That can reduce allocation work and garbage-collector pressure when the same kind of object is created frequently under load.

Go 03 Sep 2026 10 min read

Use defer for Reliable Cleanup in Go

Resource cleanup is easy to get right on the happy path and easy to miss on an early return. A function opens a file, acquires a lock, or starts a trace span; a later check fails; the function returns before reaching the cleanup statement. Go’s defer statement addresses this by scheduling a function call to run when the surrounding function returns. Used well, it places cleanup next to acquisition and makes every return path easier to reason about.

Go 03 Sep 2026 12 min read

Understand Nil Interface Values in Go

A Go program can print an error that looks empty, enter an if err != nil branch, and still be holding a nil pointer underneath. This behavior surprises developers because it seems to violate the simple rule that “nil means no value.” The rule is still consistent. The missing piece is that an interface value has two parts: a dynamic type and a dynamic value. An interface is nil only when neither part is set.

Go 02 Sep 2026 7 min read

Short Reads and Exact-Length I/O in Go

Reading bytes in Go looks simple: allocate a buffer, call Read, and inspect the error. The subtlety is that io.Reader does not promise to fill the buffer in one call. A valid reader may return fewer bytes than requested even when more data will arrive later. That behavior matters for network protocols, binary file formats, framed messages, and any code that expects an exact number of bytes. Correct stream handling starts by matching the API to the requirement: use ordinary Read when partial progress is acceptable, and use helpers such as io.ReadFull when a fixed-size field must be complete.

Go 02 Sep 2026 4 min read

Reading Streams Correctly in Go with io.Reader

Go’s io.Reader interface is tiny: type Reader interface { Read(p []byte) (n int, err error) } Its small surface hides an important contract: a read is allowed to return fewer bytes than the buffer can hold, and it can return useful bytes together with an error. Correct stream processing must handle both cases.

Go 02 Sep 2026 4 min read

Lazy Initialization in Go with sync.OnceValue and sync.OnceValues

Lazy initialization is useful when a value is expensive to build and may never be needed. The difficulty is making that initialization safe when several goroutines request the value at the same time. Go has long provided sync.Once. Since Go 1.21, the sync package also includes OnceValue and OnceValues, helpers that return functions which compute results once and reuse them for later calls. The manual sync.Once pattern A classic implementation looks like this:

Go 01 Sep 2026 7 min read

Token Bucket Rate Limiting in Go

Rate limiting protects a service from traffic spikes, accidental client loops, and workloads that consume more resources than the system can safely handle. A useful limiter should do more than enforce a fixed request count: it should allow small bursts while keeping the long-term request rate bounded. The token bucket algorithm provides exactly that behavior. This guide implements a small, concurrency-safe token bucket using only Go’s standard library and then shows how to use it in an HTTP service.

Go 01 Sep 2026 8 min read

Testing Go HTTP Handlers with httptest

HTTP handlers are one of the easiest parts of a Go service to test well. You usually do not need to bind a real network port, start the entire application, or depend on an external test framework. Go’s standard library provides net/http/httptest, which can construct HTTP requests, capture handler responses, and even start temporary HTTP servers when a real client-server round trip matters. This guide builds a small JSON endpoint and tests it at several useful levels.

Go 01 Sep 2026 7 min read

Structured Logging in Go with slog

Plain text logs are easy to print but difficult to query reliably. Once an application runs across multiple processes or containers, operators usually need to filter events by fields such as HTTP status, request path, customer ID, or latency rather than search arbitrary strings. Go’s standard library includes log/slog for structured logging. It was added in Go 1.21, so the examples in this article require Go 1.21 or newer. What structured logging changes A traditional log message often embeds data inside prose:

Go 01 Sep 2026 5 min read

Streaming Pipelines with io.Reader and io.Writer in Go

Go’s io.Reader and io.Writer interfaces are intentionally tiny, but they enable a large class of streaming programs. Files, HTTP bodies, compression streams, hashes, encoders, sockets, and in-memory buffers can all participate in the same pipeline without loading the entire payload into memory. The key design principle is to pass streams through components instead of converting them to []byte or string at every boundary. Start with the two core interfaces The standard library defines the essential contracts as methods equivalent to:

Go 01 Sep 2026 7 min read

Request Coalescing in Go Without Extra Dependencies

When many requests ask for the same expensive resource at the same time, running identical work for every caller can overload a database, API, or filesystem. A cache can help after a result exists, but it does not necessarily prevent several concurrent cache misses from triggering the same backend operation. Request coalescing solves a different problem: while one operation for a key is already running, later callers wait for that operation and share its result. After the operation finishes, the result is forgotten. The next request starts fresh work.

Go 01 Sep 2026 5 min read

Reliable Application Configuration from Environment Variables in Go

Environment variables are a convenient way to configure deployed Go services, but calling os.Getenv throughout an application makes configuration difficult to validate and test. A stronger pattern is to load configuration once at startup, parse it into typed fields, validate all invariants, and pass the resulting value to the components that need it. The examples below use only the Go standard library and work with modern supported Go releases. Keep configuration in a typed struct Suppose a service needs a listen address, request timeout, and optional log level:

Go 01 Sep 2026 2 min read

Preserve Cancellation Causes with context.WithCancelCause in Go

Go’s context.Context propagates deadlines and cancellation across API boundaries. Traditional cancellation tells downstream work that it should stop, but ctx.Err() only reports context.Canceled or context.DeadlineExceeded. Sometimes the reason matters. Go 1.20 introduced context.WithCancelCause, and later releases added cause-aware deadline helpers. They preserve a domain error without changing normal cancellation behavior. Attach a cause to cancellation package main import ( "context" "errors" "fmt" ) var ErrSuperseded = errors.New("request superseded") func main() { ctx, cancel := context.WithCancelCause(context.Background()) cancel(ErrSuperseded) fmt.Println(ctx.Err()) // context canceled fmt.Println(context.Cause(ctx)) // request superseded } Code that only understands Context still sees ordinary cancellation. Code that needs diagnostic detail can call context.Cause.