Go interview bank
Questions interviewers actually ask, each answered at the length you would say it aloud, with the topic to reread if you were not sure.
Basics
18 questions · 0 Seen01 What do len, indexing, and range mean for a Go string containing UTF-8 text? reveal ▾ hide ▴
A Go string is an immutable byte sequence, not a character array. len returns its byte count, and indexing returns the byte at that offset. A range loop decodes UTF-8: the index is the starting byte offset and the value is the decoded rune. Invalid encodings produce utf8.RuneError. Use range or unicode/utf8 when a task concerns code points. Even rune counting does not count user-perceived characters, because one grapheme may contain several code points. For example, slicing at an arbitrary byte index can split a multibyte encoding.
02 Why must code keep the slice returned by append, and how does capacity affect aliases? reveal ▾ hide ▴
append always returns the slice with the new length. If the original capacity is sufficient, it may reuse the backing array, so other slices can observe element changes when their ranges overlap. If capacity is insufficient, append allocates another array and copies the existing elements, separating the result from old aliases. The growth factor is not an API guarantee. Assign the result back, and clone when independent ownership matters. A useful boundary test runs once with len equal to cap and once with spare capacity; ignored return values and hidden aliasing often behave differently.
03 What exactly is copied when a Go struct is assigned or passed by value? reveal ▾ hide ▴
Go copies every field value into a new outer struct. Scalar fields and arrays follow their value semantics, but a copied slice still describes the same backing array, a copied map still refers to the same map, and a copied pointer still refers to the same target. An interface field may also contain a reference-like dynamic value. The result is a field-wise, not recursive, copy. Define ownership per field and clone only the mutable data that must become independent. A mutation test should prove whether changing the copy can affect the source.
04 When are deferred calls evaluated, and what can recover actually resume? reveal ▾ hide ▴
Executing defer immediately evaluates and saves the function value, receiver, and arguments; the call runs when the surrounding function exits. Deferred calls run in reverse registration order on returns and panic paths. A deferred closure instead reads captured variables when its body executes. recover stops panic unwinding only when called directly by a deferred function in the panicking goroutine. It does not resume at the panic site: discarded frames stay discarded, remaining defers run, and the function containing the recovery boundary returns to its caller. Another goroutine cannot recover the panic.
05 How do errors.Is and errors.As differ, and why are direct equality and type assertions fragile? reveal ▾ hide ▴
errors.Is asks whether an error tree contains or custom-matches a target value. errors.As finds the first error assignable to a requested type and stores it in the supplied target. Both traverse wrappers and joined branches. Equality and a direct type assertion inspect only the outer value, so adding contextual wrapping breaks them. Use Is when control flow depends on a stable category and As when code needs documented fields. Keep Error text for people rather than parsing it. Also avoid returning a typed nil pointer as error, because that interface value is non-nil.
06 How does %w differ from %v in fmt.Errorf, and why is wrapping an API decision? reveal ▾ hide ▴
Both verbs include the cause in the message, but only %w makes the returned error unwrap to that cause. That allows errors.Is and errors.As to find it through later wrappers. Once an exported function wraps a sentinel or dependency error, callers can build control flow around that match; replacing the dependency can then become a breaking change. Wrap causes that callers are meant to recognize. Otherwise translate them to a stable domain error while preserving useful text internally. With errors.Join, remember that the result is a tree, not a single linear chain.
42 When are a deferred call’s function, arguments, and captured variables evaluated? reveal ▾ hide ▴
In Go 1.27, executing defer f(x) immediately evaluates and saves the function value and argument x; only the call waits until the surrounding function exits. A deferred closure behaves differently for free variables: its body reads captured variables when it finally runs unless values are passed as parameters. Deferred calls execute last-in, first-out on normal return, panic unwinding, and runtime.Goexit. Reassignment after registration therefore does not replace a saved function or receiver, but it can change what a closure observes. Tests should mutate state between registration and return and assert both order and values.
43 Where must recover run, and what control flow resumes after it succeeds? reveal ▾ hide ▴
In Go 1.27, recover stops a panic only when called directly by a deferred function in the same goroutine while that goroutine is unwinding. A parent goroutine cannot recover a child, so each isolated job or request needs its own boundary near the goroutine entry point. After recovery, execution does not resume after the panicking call; remaining defers run and the recovering function returns to its caller. Preserve the panic value and stack, then report an explicit task failure. Broad recovery can hide corrupted invariants, so re-panic when the boundary cannot guarantee usable state.
44 How can a nil pointer become a non-nil error interface? reveal ▾ hide ▴
In Go 1.27, an interface value is nil only when both its dynamic type and dynamic value are absent. Assigning a nil *FieldError to an error supplies the dynamic type *FieldError, so the interface compares unequal to nil even though its pointer value is nil. Formatting it may also panic if Error() dereferences the receiver. Return a literal nil on success and construct the concrete error only on failure. Add a success-path assertion that err == nil; reflection or pointer comparison inside callers is not a sound repair for a broken producer contract.
45 Why should reusable Go code usually return an error instead of logging it? reveal ▾ hide ▴
In Go 1.27, lower layers should normally add useful operation context and return the error while preserving any documented chain. The request, worker, command, or process boundary owns the final outcome and can log once with stable metadata. Logging and returning at every frame duplicates one failure, loses correlation, and risks exposing payloads or credentials in messages. It also makes tests depend on internal call depth. A boundary must still classify cancellation and expected domain errors before choosing level and response. The trade-off is that returned errors need enough context without publishing dependency details callers were never meant to inspect.
46 When should fmt.Errorf use %w rather than %v for an underlying error? reveal ▾ hide ▴
In Go 1.27, %w creates a traversable wrapper, so errors.Is and errors.As can still find the underlying value or type. %v includes only its text and cuts off that structure. Choose %w only when callers are meant to depend on the cause; exposing a database-driver sentinel by wrapping it makes that dependency part of the package API. Otherwise translate to a package-owned sentinel or structured error and keep low-level detail in controlled diagnostics. Guard err != nil before wrapping, because formatting a nil error can produce a non-nil error and turn success into failure.
47 How should code inspect an error produced by errors.Join? reveal ▾ hide ▴
In Go 1.27, errors.Join produces an error with multiple children through Unwrap() []error, so the structure is a tree rather than one linear chain. Use errors.Is to match a documented sentinel and errors.As with a pointer to a target variable to extract a documented type; both traverse standard single-child and multi-child wrappers. Manual loops that expect only Unwrap() error miss branches. Joining independent validation failures preserves classification, but it does not define presentation order as a public protocol. Tests should match every promised category through at least one outer wrapper instead of comparing the combined message.
48 How do untyped constants differ from variables during assignment and conversion? reveal ▾ hide ▴
In Go 1.27, an untyped constant can retain an exact value until a concrete context supplies a type. Assignment succeeds only if that value is representable in the destination; the same constant may fit uint8 at one value and fail at another. A variable already has a fixed compile-time type and ordinary numeric variables are not implicitly converted. Explicit conversions can truncate or wrap according to the language rules, so validate ranges before narrowing. Formatting an untyped constant can also apply its default type. Test boundary values rather than inferring safety from one small literal.
49 What changed about loop-variable capture in Go 1.22 and later? reveal ▾ hide ▴
Code compiled with the Go 1.22 or later language semantics gives each iteration its own instance of variables declared by := in a range clause or for initializer. Closures and goroutines created in the loop therefore capture that iteration’s variable rather than all sharing the final value, making the old value := value workaround generally redundant for Go 1.27 code. Variables declared outside the loop and assigned with = remain shared. Check the module language version and declaration form before deleting or adding a workaround, and still test concurrency because captured pointed-to data may itself be shared.
62 How do length and capacity change the correct way to preallocate a slice? reveal ▾ hide ▴
In Go 1.27, make([]T, n) creates a slice with length n, so all positions already exist and should normally be filled by index. make([]T, 0, n) creates an empty slice with capacity for about n appended elements. Appending n values to the first form produces 2n elements, with the initial zero values still present. Capacity is an allocation hint and boundary for reslicing, not the number of logical records. Review allocation together with the write pattern, and test zero values because they can make the doubled prefix look superficially valid.
63 What can a full slice expression prevent, and what can it not prevent? reveal ▾ hide ▴
In Go 1.27, a full slice expression such as window := whole[i:j:j] sets window’s capacity equal to its length. Appending through that slice must allocate new storage rather than overwrite elements after j in the original backing array. It does not stop mutations to the visible elements, deep-copy referenced element contents, or release the original array while the window remains reachable. A tiny long-lived subslice can therefore retain a large buffer. Use slices.Clone when ownership or retention requires independent top-level storage, accepting the allocation, and verify real retention with memory profiles.
64 Why is struct embedding composition rather than inheritance? reveal ▾ hide ▴
In Go 1.27, embedding declares a real field whose unqualified type name is its field name. Eligible fields and methods may be promoted for shorter selectors, but the outer value is not a subtype of the embedded type and composite literals must initialize the embedded field explicitly. A selector is usable only when one candidate exists at the shallowest depth; same-depth conflicts are ambiguous. An outer method can shadow a promoted name, while the full path still reaches the embedded member. Design ownership through explicit composition and separately assert method sets for T and *T, because promotion differs for value and pointer embedding.
65 When is a Go struct comparable, and why may == still be the wrong equality? reveal ▾ hide ▴
In Go 1.27, a struct is comparable only if every non-blank field type is comparable; then == compares corresponding fields, and the struct may be a map key. A slice, map, or function field makes the whole struct non-comparable. Even when compilation allows ==, field equality may not express the domain rule: timestamps may need normalization, caches may be irrelevant, and floating-point NaN is unequal to itself. Write a named equality function over contract fields when business meaning differs. Do not compare padding or raw bytes, and test values that differ only in ignored fields.
Types and interfaces
19 questions · 0 Seen07 How does implicit interface satisfaction shape API design, and where does the typed-nil trap appear? reveal ▾ hide ▴
A type satisfies an interface by having its required method set; no declaration links the two packages. That lets the consuming package define a small interface around the behavior it actually uses. Add compile-time assignments for important implementations so signature changes fail near the contract. An interface is nil only when both its dynamic type and dynamic value are absent. Storing a nil *ParseError in an error records a dynamic type, so err != nil. Return literal nil on success, and construct the concrete error only on failure.
08 How do value and pointer receivers affect mutation, copying, and interface satisfaction? reveal ▾ hide ▴
A value receiver receives a copy, while a pointer receiver can modify the original value and avoids copying a large struct or a lock. Method sets are the separate interface boundary: methods on T belong to both T and *T, but methods on *T belong only to *T. Although x.M() may compile through an automatic address-taking rewrite when x is addressable, interface assignment performs no such rewrite. Choose receivers from semantics, keep the choice consistent, never copy a used sync primitive, and assert whether T, *T, or both should satisfy an interface.
09 When should a Go API use type parameters rather than ordinary interface values? reveal ▾ hide ▴
Use a type parameter when the signature must preserve a concrete type relationship across inputs, callbacks, containers, or results. Use an ordinary interface when the implementation only needs behavior such as Read and runtime substitution is the purpose. Constraints describe permitted operations through type sets; they do not perform implicit conversions. Keep them minimal, use ~ only when named types with the same underlying type should qualify, and remember that comparable does not mean ordered. A parameter used once under any often adds no information, while a constraint-only interface cannot hold ordinary runtime values.
10 Which map behaviors must an API make explicit around absence, order, sharing, and concurrency? reveal ▾ hide ▴
A lookup returns the element zero value whether a key is absent or stores that value, so use v, ok := m[k] when presence matters. A nil map supports reads, range, delete, and clear, but assignment panics. Map assignment shares the same underlying map; clone when handing out an independent snapshot. Range order is unspecified, so sort keys before stable output. Finally, ordinary maps do not support unsynchronized concurrent writes or a write racing with a read. Protect every alias with the same synchronization policy, not just one variable.
11 What makes a reflect.Value safe to modify, and what checks belong before Set or Call? reveal ▾ hide ▴
A modifiable reflect.Value must represent caller-owned writable storage, usually reached with reflect.ValueOf(&target).Elem(), and CanSet must be true. Addressability alone is insufficient because visibility rules still protect unexported fields. Before Set, verify validity, expected Kind, exact Type assignability or an intentional conversion, and nil state where applicable. Before Call, validate the method, argument count, and argument types; reflection bypasses compile-time checking and bad inputs panic. Cache immutable Type metadata when useful, but do not assume reflection makes the underlying value safe for concurrent mutation.
50 How do you derive the right constraint for a generic Go function? reveal ▾ hide ▴
In Go 1.27, a generic body may use only operations supported with the same meaning by every type in its parameter’s type set. Start by listing operations the algorithm needs: moving values requires any, equality may require comparable, ordering can use cmp.Ordered, and a behavior can be expressed by a method element. Use ~int rather than int when named types with underlying int should participate. Keep the constraint no narrower than the real contract. A caller-provided comparison function is often better for domain ordering, and compile cases should include accepted named types plus deliberate rejections.
51 Why is returning the zero value of T often an incomplete generic API? reveal ▾ hide ▴
In Go 1.27, var zero T produces the zero value of the type chosen at instantiation, but generic code cannot assume that value means absence. Zero, an empty string, or a nil pointer may be valid stored data. Lookup, pop, and parse APIs should normally return (T, bool) or (T, error) so callers can distinguish success from failure. A generic container should also decide whether its own zero value works; a slice-backed stack can append immediately, while a map-backed type needs construction or lazy initialization. Test successful zero-valued elements as well as empty and failure paths.
52 Why can value.M() compile while the value still fails an interface assignment? reveal ▾ hide ▴
In Go 1.27, the compiler may rewrite value.M() as (&value).M() when value is addressable and M has a pointer receiver. That call convenience does not add M to the method set of the non-pointer type. For a named type T, T has value-receiver methods, while *T has both value- and pointer-receiver methods. Interface assignment uses actual method sets and does not take an address automatically. Add compile-time assertions for both intended forms, and test map elements or returned values because they may not have the addressability of a local variable.
53 When can comparing two interface values panic in Go? reveal ▾ hide ▴
In Go 1.27, interface equality first considers dynamic types. If both interfaces hold the same dynamic type, their dynamic values must then be comparable. Holding a slice, map, or function makes that comparison panic even though the interface type itself can appear in an equality expression or as a map key type. A nil interface also differs from an interface holding a typed nil pointer. Do not use any values as stable keys without validating their dynamic types, and do not deduplicate heterogeneous input through interface equality blindly. Tests should include slices, typed nils, and distinct comparable concrete types.
54 Why can’t code assign directly to a field of a struct stored in a map? reveal ▾ hide ▴
In Go 1.27, a map index expression yields a value but not an addressable variable, because map growth and implementation details may relocate entries. If profiles has type map[ID]Profile, profiles[id].Name = name does not compile. Read the struct into a local value, modify it, and assign the complete value back. Changing the map to store *Profile permits field mutation, but it also introduces shared identity, nil-pointer cases, aliasing, and synchronization obligations. Choose pointer values only when those semantics are intended, and test absence separately from a stored zero-valued struct.
55 What does Go guarantee when a map is mutated during range? reveal ▾ hide ▴
In Go 1.27, deleting a map entry that has not yet been reached guarantees that entry will not be produced by the current range. This makes in-place deletion filtering valid in one goroutine. An entry added during iteration may be produced or skipped; code must not rely on either outcome. Iteration order is unspecified as well. If new entries require processing, use a separate work queue or second phase. None of these sequential rules permits unsynchronized concurrent mutation: every overlapping read and write through every alias still needs one synchronization policy, verified with the race detector.
56 What receiver does a Go method value retain? reveal ▾ hide ▴
In Go 1.27, evaluating x.M immediately evaluates and saves its receiver, producing a function that no longer accepts that receiver explicitly. For a value-receiver method, saving performs an ordinary shallow copy of x; later replacement of scalar fields in the original is not seen, though referenced slices or maps may still share storage. For a pointer-receiver method, the method value saves the pointer, so later mutations through that object remain visible. Tests should create the callback, mutate the original, then invoke it. Use a method expression such as T.M when callers should supply the receiver explicitly.
57 Can you add methods to a type declared in another Go package? reveal ▾ hide ▴
In Go 1.27, a method’s receiver base type must be a non-pointer, non-interface type defined in the current package. You cannot attach methods to an imported type, an alias denoting it, or an already defined pointer type. Define a new local named type when distinct semantics and conversions are acceptable, wrap or embed the external value when you need composition, or write an ordinary function accepting it. A new defined type does not automatically inherit the original method set, while embedding promotes methods under specific rules. Choose deliberately because conversions and wrappers affect API compatibility and identity.
58 What do reflect.TypeFor and reflect.TypeAssert provide in Go 1.27? reveal ▾ hide ▴
In Go 1.27, reflect.TypeFor[T]() obtains the reflect.Type token for a compile-time type parameter without manufacturing a value, including when the zero value would be nil. reflect.TypeAssert[T](v) performs the typed extraction associated with a reflect.Value and reports success, giving generic adapters a checked boundary instead of scattering Interface().(T) assertions. These helpers do not make an invalid Value safe or bypass export, assignment, and nil rules; validate the value state first. They also raise the minimum toolchain version, so keep go.mod, CI, and generated code aligned with Go 1.27.
59 When should reflective metadata be cached, and what must remain uncached? reveal ▾ hide ▴
In Go 1.27, reflect.Type values are comparable and make useful keys for immutable plans such as validated field-index paths, tag interpretations, and method signatures. Cache only after a representative benchmark shows repeated discovery matters, publish entries safely, and define a bound if runtime-generated types can grow without limit. Do not cache mutable reflect.Value handles as if reflection detached them from the original object; concurrency safety remains that of the underlying value. Embedded-field ambiguity and package-specific tag rules belong in plan construction. Prefer generics, interfaces, or generated code when the type relationship can stay compile-time checked.
60 How do an invalid reflect.Value, a typed nil, and a zero value differ? reveal ▾ hide ▴
In Go 1.27, reflect.ValueOf(nil) is invalid: IsValid() is false and most other operations panic. An interface holding a nil pointer produces a valid Value with Kind() Pointer and IsNil() true. A concrete zero value created with reflect.Zero(typ) is valid, has that type, and may be non-nil depending on the type. Check validity before Kind; call IsNil only for nil-capable kinds; check nil before Elem. A generic dereference loop that skips this order turns ordinary bad input into a reflection panic instead of a controlled error.
61 Why is MethodByName an authorization boundary rather than just a lookup? reveal ▾ hide ▴
In Go 1.27, MethodByName can find an exported method in the current method set, but existence proves neither that an external requester may call it nor that its signature matches decoded arguments. Passing an untrusted string directly can expose every reachable exported method as a remote operation. Map external commands to a fixed allowlist, authenticate and authorize first, then validate method validity, full function type, argument count, assignability, results, and panic policy before Call. Reflection removes compile-time checks and bad calls panic. Prefer an explicit handler map or interface when the command set is known.
66 How do Go 1.27 method type parameters differ from receiver type parameters? reveal ▾ hide ▴
Go 1.27 permits a method to declare additional type parameters after its method name, such as Apply[U any]. These belong to that operation. Receiver parameters in func (s Stack[T]) ... instead redeclare, by position, relationships already owned by the generic base type; their names may differ from the base declaration. Before Go 1.27, a new U generally required a standalone generic function, so the syntax raises the module’s minimum language version. Interface methods still cannot declare this extra method-owned parameter form. Use it only when one operation introduces the relationship, and keep go.mod and CI pinned accordingly.
67 Why can code instantiated with comparable still panic during equality? reveal ▾ hide ▴
In Go 1.27, the constraint-satisfaction exception introduced in Go 1.20 allows an ordinary comparable interface type such as any to satisfy comparable, even though not every dynamic value stored in it is strictly comparable. Equal[any] can compile, but comparing two interface values whose identical dynamic type is a slice, map, or function panics. The same risk applies to map[any]V insertion and lookup. Prefer concrete comparable key types. If an interface boundary is unavoidable, validate dynamic types before use and test slices, maps, functions, typed nils, and ordinary scalar values.
Concurrency
4 questions · 0 Seen12 Who should close a channel, and how do you prevent a goroutine from being stranded on it? reveal ▾ hide ▴
The goroutine that owns the send side and knows no more values will be produced should close the channel. Receivers normally detect completion through range or the comma-ok result; they should not close a shared input merely because they are done. Sending to or closing a closed channel panics, while receiving from a closed channel yields buffered values and then the zero value with ok false. Every potentially blocking send and receive needs a termination path, often select on ctx.Done(). The caller should also have a way to wait for workers so cancellation does not leave goroutines behind.
13 How do nil channels, default, and multiple ready cases change a select statement? reveal ▾ hide ▴
A case using a nil channel can never proceed, so assigning nil is a useful way to disable that case dynamically. If one or more communications are ready, select chooses one ready case; code must not depend on a fixed winner or use selection as a priority guarantee. If none is ready, default runs when present; otherwise the goroutine blocks. A default case therefore changes backpressure into polling or dropped work and can create a busy loop. For cancellation, include ctx.Done() and make every blocking output operation cancellation-aware as well as every input.
14 How do you choose among a mutex, atomic operation, channel, and WaitGroup? reveal ▾ hide ▴
Choose by the invariant, not by a blanket performance rule. A mutex protects related mutable state and makes a multi-field transition atomic. sync/atomic suits a small independent counter, flag, or pointer with a precisely defined memory protocol; composing several atomic fields does not create one transaction. A channel transfers data or ownership and can provide backpressure, but it is not automatically simpler than a lock. A WaitGroup only tracks completion; it does not protect results or carry errors. Add before launching workers, avoid copying synchronization values after use, and verify the design with the race detector.
15 What makes a worker pool or pipeline cancellation-safe and bounded? reveal ▾ hide ▴
Bound concurrency with a fixed worker count and, where useful, a deliberately sized queue. The producer owns closing the jobs channel; a coordinator closes the results channel only after all workers finish. Every stage must select on cancellation while receiving and while sending, because a downstream consumer can stop first and strand an upstream goroutine. Decide whether queued work is drained or abandoned, propagate the first error or an aggregate through an explicit channel, and wait for all goroutines before returning. Buffering can absorb bursts, but it cannot repair missing lifecycle ownership.
Standard library
9 questions · 0 Seen16 When should you choose bufio.Scanner, Reader, or Writer, and what boundary must each handle? reveal ▾ hide ▴
Scanner is convenient for tokenized input such as lines or words, but callers must check Scanner.Err and raise its buffer limit before scanning unusually large tokens. Reader gives finer control for delimiters, peeking, and partial data, so its code must handle returned bytes together with io.EOF. Writer batches small writes, but data is not guaranteed to reach the wrapped writer until Flush succeeds. Choose buffering to reduce small underlying operations, not to load an entire stream blindly. For a bulk transfer, io.Copy may already provide the simpler loop.
17 What is the correct ownership and propagation contract for context.Context? reveal ▾ hide ▴
Pass Context explicitly as the first parameter and propagate it into every blocking downstream call. The creator of a derived context owns the returned cancel function and should call it, even when the deadline later expires, so timers and links are released promptly. Cancellation is a signal, not a forced goroutine stop; work must observe Done or use context-aware APIs. Use values only for request-scoped metadata that crosses API boundaries, with private key types, not for optional parameters or dependencies. A child cannot extend an earlier parent deadline.
18 What does //go:embed guarantee, and which path and lifecycle limits matter? reveal ▾ hide ▴
//go:embed selects files at build time and initializes a package-level string, []byte, or embed.FS variable. A string or []byte directive may have one pattern matching one file; embed.FS can hold a tree. Patterns are relative to the package directory, cannot escape it, and must match. The file system is read-only, and its content is a build snapshot rather than a live disk view. Directory walks normally omit names beginning with dot or underscore; an all: prefix includes them. Use fs.Sub to expose a subtree, and remember that embedded assets increase the binary and require rebuilding to change.
19 How does encoding/gob match values across an encode-decode boundary, especially through interfaces? reveal ▾ hide ▴
Gob transmits type descriptions and matches compatible struct fields by name, so exported fields can be added or omitted when sender and receiver types remain compatible. Unexported fields are not serialized. When a value travels through an interface, the decoder also needs the concrete type registration, normally performed deterministically during initialization on both sides. Reuse an Encoder or Decoder for a stream so type information is not repeatedly sent. Gob is Go-specific and convenient for trusted Go-to-Go communication, but it is a poor public or cross-language format and still requires an explicit schema-evolution policy.
20 When should XML processing use Decoder.Token or DecodeElement instead of Unmarshal? reveal ▾ hide ▴
Use Unmarshal when the document is bounded and maps cleanly to one in-memory struct. Use Decoder.Token for a large stream or when processing depends on start elements, end elements, attributes, or namespaces. Once a relevant StartElement appears, DecodeElement can decode just that subtree and leave the stream positioned afterward. Always check the terminal error and treat io.EOF as normal completion. Struct tags describe mapping, not business validation or a full XML Schema contract. Enforce size and nesting limits at the input boundary, then validate required fields and domain rules separately.
21 How must code handle the n and err returned by io.Reader and io.Writer? reveal ▾ hide ▴
A Reader may return n greater than zero together with a non-nil error, including io.EOF, so process p[:n] before acting on err. Repeatedly treating any error first can silently discard the final bytes. A Writer must report how many bytes it accepted; returning fewer than len(p) without a non-nil error violates the contract, and callers commonly translate that to io.ErrShortWrite. io.Copy correctly owns the transfer loop, but it does not generally close either endpoint. The code that acquires a resource owns closing it and must decide whether a close or flush error affects the result.
22 What checks make decoding one JSON request body a strict protocol boundary? reveal ▾ hide ▴
First limit the body size, then decode into a purpose-built struct rather than map[string]any. Decoder.DisallowUnknownFields can reject unknown object fields, but it does not enforce business rules, distinguish every omitted value from a zero value, or reject a second top-level JSON value. Decode once, validate required ranges and relationships, then decode again and require io.EOF. Use pointers or an explicit optional type when absence differs from zero. If arbitrary numbers must retain their textual precision before validation, enable UseNumber instead of accepting float64 conversion through interface values.
23 How do you choose between byte operations, rune operations, and strings.Builder? reveal ▾ hide ▴
Choose bytes for encoded protocols and exact UTF-8 storage, and choose runes when the operation is defined on Unicode code points. Neither automatically implements grapheme-aware user text. strings functions preserve string immutability and are preferable to hand-written scans when they express the operation. For incremental output, strings.Builder avoids repeatedly copying the accumulated prefix; call Grow only from a justified size estimate and do not copy a non-zero Builder. For delimited data, use the format parser rather than Split, because quoting and escaping are protocol rules. Test ASCII, multibyte text, invalid UTF-8 where allowed, and empty input.
24 Why is html/template safer than text/template for HTML, and when can that safety fail? reveal ▾ hide ▴
html/template performs context-aware escaping, so the same untrusted value is encoded appropriately for HTML text, attributes, URLs, JavaScript, or CSS positions. text/template only performs textual substitution and is not an HTML security boundary. Safety fails when code converts untrusted data to template.HTML, template.JS, or another trusted content type, because that conversion asserts the content is already safe and bypasses normal escaping. Keep templates structurally fixed, pass untrusted input as data, validate URLs and application policy separately, and check both parse and execution errors. Parse reusable templates once rather than per request.
Services and tooling
17 questions · 0 Seen25 What makes a Go microbenchmark trustworthy enough to compare two implementations? reveal ▾ hide ▴
Prefer for b.Loop() in new benchmarks. It excludes setup before the first call and cleanup after the loop from timing, keeps relevant loop values alive, and runs the benchmark function once per measurement. Legacy b.N benchmarks require explicit timer control and protection against dead-code elimination. Use ReportAllocs or -benchmem when allocations matter. Keep workloads, CPU settings, and environments comparable, collect multiple samples with -count, and compare them with benchstat rather than one ns/op value. Sub-benchmarks should vary one named dimension. A microbenchmark measures this workload on this machine; it does not prove production latency.
26 How should you reason about middleware order and request context in a chi service? reveal ▾ hide ▴
chi middleware wraps an http.Handler, so request-side code runs in registration order until it calls the next handler, and response-side code resumes in reverse order. Put request IDs and recovery outside components that must always be observed; put authentication before protected handlers. Route parameters are attached to the request context and should be read with chi helpers, while application metadata should use collision-resistant key types. Keep business handlers dependent on net/http contracts where possible. A middleware that writes an error response must stop the chain, and tests should cover both allowed and rejected paths to prove ordering.
27 What lifecycle does database/sql require for pools, rows, and transactions? reveal ▾ hide ▴
sql.DB is a concurrency-safe connection pool handle, not one persistent connection. Create and reuse it, verify startup connectivity with PingContext when required, and tune open, idle, and lifetime limits against database capacity. QueryContext returns Rows that must be closed; consume them and check rows.Err after iteration so late driver errors are not lost. For a transaction, begin with context, defer Rollback as a harmless fallback, execute every operation through tx, and call Commit only after all checks succeed. QueryRow reports sql.ErrNoRows from Scan, not from the call itself.
28 What boundaries keep an Echo handler safe around context reuse, binding, and errors? reveal ▾ hide ▴
Treat echo.Context as request-scoped and do not retain it or access it from background goroutines after the handler returns, because the framework may reuse it. Bind into a dedicated input type, then validate authorization and domain rules separately; successful binding is not approval. Prefer returning an error to a centralized HTTP error handler so one component chooses the status and response shape. If middleware or a handler writes a response directly, return immediately to avoid a second write. Middleware follows an onion model around next, so registration order determines which failures, timing, and logs an outer layer can observe.
29 Why is retaining Fiber request data after a handler returns unsafe? reveal ▾ hide ▴
Fiber builds on fasthttp, which reuses request contexts and buffers to reduce allocations. Values exposed by parameters, queries, or body access can therefore refer to storage recycled for a later request. A goroutine or cache that retains those values may observe corrupted or cross-request data. Finish request work before returning, or copy the exact bytes or strings needed into owned storage before handing them to asynchronous code. Do not pass *fiber.Ctx itself across the boundary. The same lifecycle rule applies to middleware: call Next deliberately, return its error, and stop after writing a terminal response.
30 How do you design a useful Go fuzz test and turn a failure into a regression test? reveal ▾ hide ▴
Seed the fuzz test with valid examples and important edge classes, then state a property rather than an expected answer for every generated input. Good properties include no panic, round-trip preservation, normalization idempotence, or agreement with a simpler oracle. Bound input-derived allocation and recursion so the harness tests the target instead of exhausting the machine. When fuzzing finds a failure, reproduce it with the saved input, fix the cause, and keep that corpus entry under testdata/fuzz so ordinary go test runs it later. A passing fuzz run only covers the explored time and corpus; it is not a proof.
31 How should a Gin handler separate binding, validation, aborting, and response writing? reveal ▾ hide ▴
Bind into a request-specific struct and treat syntax, field validation, authorization, and domain checks as separate decisions. ShouldBind methods return an error and let the handler choose a consistent response; must-bind methods may write a 400 response and abort the chain, reducing that control. In middleware, Abort prevents remaining handlers from running, but it does not return from the current Go function, so write the terminal response and return explicitly. Never continue into business logic or write a success body after an error. Tests should assert status, body, side effects, and that downstream handlers were not called.
32 Why is go generate not a build dependency system, and how should a team make it reproducible? reveal ▾ hide ▴
go generate scans //go:generate directives and runs their commands only when explicitly invoked; go build and go test do not run it automatically. It does not analyze dependencies or know when output is stale. Treat generation as a controlled source transformation: pin generator versions, make output depend only on declared inputs, sort unstable data, format the result, and mark generated files. Decide whether generated files are committed. In either policy, CI should run the documented command and fail on an unexpected diff or missing output. A directive executes tools, so review it with the same trust as a script.
33 What does go vet contribute beyond compilation, and what does a clean result not prove? reveal ▾ hide ▴
go vet runs analyzers for suspicious constructs that are legal Go but often wrong, such as malformed formatting calls, copied lock values, and unreachable or ineffective patterns covered by the selected analyzers. Compilation proves syntax and type correctness, not those intent-level contracts. Run vet consistently in CI with the same module and build context, understand any enabled third-party analyzers, and suppress findings narrowly with a documented reason. A clean result does not prove race freedom, security, complete error handling, or business correctness. Keep tests, the race detector, fuzzing, and review as separate evidence.
34 How do you keep a GORM transaction atomic and its relationship queries predictable? reveal ▾ hide ▴
Use db.Transaction with a callback and perform every operation through the tx handle passed to that callback. Returning an error triggers rollback; returning nil commits, so never accidentally call the outer db inside the unit of work or swallow a failure. For relationships, choose Preload, joins, or explicit queries from the required result shape instead of relying on implicit per-row loading that creates N+1 traffic. Log and test query counts for representative data. ORM tags and hooks do not replace database constraints, and bulk updates or deletes need explicit predicates and transaction tests.
35 What lifecycle and resource limits turn a net/http handler into a production server? reveal ▾ hide ▴
Configure an http.Server rather than relying on an unbounded convenience call: set appropriate header-read, read, write, and idle timeouts for the protocol and workload, and cap request bodies before decoding. Propagate r.Context into downstream work so client disconnects and server cancellation can stop it. On shutdown, stop accepting new work, call Server.Shutdown with a deadline, and let active handlers finish within that budget; separately manage hijacked or other long-lived connections. Handlers must choose one response path, set headers before writing the body, and avoid launching untracked background goroutines.
36 How does a Go module choose dependency versions, and why can replace hide release failures? reveal ▾ hide ▴
The module graph records minimum requirements, and minimal version selection chooses the highest required version for each module path across that graph. go.mod expresses the build list inputs; go.sum authenticates downloaded module content but does not lock one complete environment like a traditional lockfile. go mod tidy adds requirements needed by packages and tests and removes unused ones. A replace directive in the main module can redirect a dependency to another version or local directory, but downstream users do not inherit that local setup. Test release candidates without unpublished replaces, and commit both module files when their changes are intentional.
37 How do you choose and interpret a pprof profile without overclaiming from the data? reveal ▾ hide ▴
Start from the symptom. Use a CPU profile for where sampled execution time accumulates, heap profiles for retained memory, allocation profiles for allocation pressure, and block or mutex profiles for sampled contention. A profile aggregates stack samples over a workload; it is not an exact trace of one request and small differences may be noise. Capture representative, bounded periods, preserve labels and build identity, and compare like workloads before and after a change. Protect net/http/pprof endpoints because they expose internals and consume resources. Use execution tracing when the question is scheduler timing or causal event order.
38 What makes slog output useful and safe across a production service? reveal ▾ hide ▴
Emit stable message names and structured attributes with consistent keys, then configure level filtering and formatting in the Handler rather than at every call site. Carry request metadata by deriving a Logger with With or by passing context where the handler uses it; do not store a request logger in global mutable state. Check Enabled before computing an expensive attribute. Treat logging as a data-export boundary: redact credentials, tokens, personal data, and oversized payloads before constructing the record. Log an error once at the boundary that owns the outcome, while lower layers wrap and return it with useful context.
39 What does sqlx simplify, and which SQL boundaries still remain the application responsibility? reveal ▾ hide ▴
sqlx reduces scanning and binding boilerplate but keeps database/sql semantics. Get and Select map rows into destinations, while StructScan depends on predictable column-to-field mapping; explicit column lists and aliases avoid silent ambiguity. Named parameters improve readability but values must still travel as arguments, never string interpolation. For a slice in an IN clause, use sqlx.In to expand placeholders and arguments, then Rebind for the target driver. Context cancellation, pool sizing, rows and transaction lifecycles, null handling, and query performance remain application responsibilities. Test zero rows, duplicate column names, empty input slices, and rollback paths.
40 What makes a table-driven Go test with subtests reliable rather than merely compact? reveal ▾ hide ▴
Each case should name one behavior boundary and carry independent inputs and expected results. Run it with t.Run so failures are selectable and readable, and compare the observable contract rather than implementation details. Mark helper functions with t.Helper and register owned cleanup with t.Cleanup so it runs even when the test exits early. Parallel subtests must not share mutable fixtures, environment variables, ports, clocks, or mocks without synchronization and isolation. Test error categories with errors.Is or errors.As instead of message text. Coverage shows executed code, not assertion quality, so include negative paths and side-effect checks.
41 When does the Go execution tracer answer a question that pprof cannot? reveal ▾ hide ▴
Use tracing when event order and timing matter: goroutine creation, runnable delay, blocking and unblocking, network waits, syscalls, garbage collection, and processor scheduling. pprof aggregates sampled stacks and is better for finding where CPU, allocations, or contention accumulate, but it usually cannot reconstruct a causal timeline. Trace a short representative window because files and overhead grow quickly, and add tasks, regions, and logs with propagated context to mark application work. Compare the trace with metrics and profiles before assigning causality. A waiting goroutine is not automatically a leak; prove that its owner and termination path are missing.
No questions match this filter.