Idioms and API Design
Go leaves most design decisions to convention rather than to the compiler, so an interview asks you to defend the name, the constructor and the exported surface you chose, and the ones you did not.
part ofGo (Golang)overview, primer and where to startread it →on this pageshowhide
explore
- Language Trade-Offs17 questions
- Errors Instead of Exceptions4 questions
- When Type Parameters Pay5 questions
- Composition Over Inheritance4 questions
- What Go Deliberately Omits4 questions
- Readability Conventions20 questions
- Avoiding Stuttered Identifiers4 questions
- Receiver Names3 questions
- MixedCaps and Initialisms5 questions
- Doc Comments and Examples4 questions
- Early Returns and Nesting4 questions
- Value Construction14 questions
- Useful Zero Values4 questions
- Variadic Option Functions5 questions
- Config Structs and Defaults5 questions
- Package Boundaries23 questions
- Keeping Surfaces Small5 questions
- Signature Conventions4 questions
- Who Starts the Goroutine5 questions
- Wiring Dependencies by Hand4 questions
- Evolving Without Breaking Callers5 questions
- Anti-Patterns Reviewers Reject13 questions
- Premature Interfaces5 questions
- Global State and init4 questions
- Discarded and Swallowed Errors4 questions
questions
87 · 5 sectionsGo has no `extends` keyword - how do you reuse another type's behaviour inside a struct?
basics
~20 sGo gives three choices: embed the type anonymously so its methods are promoted onto yours, hold it in a named field and forward only the calls you want, or depend on an interface it already satisfies.
Why does os.Open return (*os.File, error) instead of throwing an exception when the file is missing?
basics
~20 sIn Go a failure is an ordinary return value of the predeclared error interface type. os.Open hands back a file and an error together, so the caller checks it on the spot and every failure path stays visible.
When a Go function returns a non-nil error, what may the caller assume about its other return values?
basics
~20 sNothing useful, unless the documentation says otherwise. By convention the other results are their zero values on failure, so a returned pointer is nil. Using one before returning is the classic nil dereference panic in Go code.
In Go, why is `func Handle[T any](v T)` no more capable than `func Handle(v any)`?
basics
~20 sA type parameter constrained by any permits no operations — not even ==. With T appearing in only one position it links nothing to nothing, so the generic form does exactly what the plain any parameter does, with more syntax.
Why does Go make every numeric conversion explicit, and what does that still not catch?
basics
~20 sGo performs no implicit numeric conversion: mixing an int with an int64, or an int with a time.Duration, is a compile error until you write the conversion. But the conversion you wrote can still truncate or lose precision silently.
Why does Go write a multi-word identifier as maxRetryCount rather than max_retry_count?
basics
~20 sGo's convention is MixedCaps: each following word is capitalised and underscores are dropped. Case is already meaningful, since the first letter decides package visibility, so it doubles as the word separator. Underscores belong to file names.
Why should a Go doc comment on an exported function begin with the function's name?
basics
~20 sGo's documentation tools lift the comment away from the declaration and show it on its own, so the sentence has to name its own subject. Starting with the identifier also makes the first sentence a self-contained one-line summary in package listings.
In Go, why is the success path never put inside the `else` of an `if err != nil` check?
basics
~20 sIdiomatic Go handles the failure inside the if and returns, leaving the success path unindented below it. Because Go checks an error after nearly every call, putting each success in an else pushes the real work rightward.
In Go, why is a type named http.HTTPClient worse than http.Client?
basics
~20 sEvery reference outside the package already carries the package name, so http.HTTPClient says HTTP twice at the call site. Go names a type for how it reads after the qualifier, so inside package http the type is simply Client.
Why is Go's handler method named ServeHTTP rather than ServeHttp, and how is an initialism cased in an unexported name?
basics
~10 sGo keeps an initialism in one uniform case, all upper or all lower, never title case: ServeHTTP, URL, userID. In an unexported name a leading initialism goes fully lowercase, as in xmlHTTPClient.
How should a Go constructor New(cfg Config) supply defaults for fields the caller left at zero?
basics
~20 sNew takes the Config by value, replaces each field still at its zero value with a named package default, and builds the object from that copy. The caller's struct is untouched, and every caller gets identical defaults.
Why do Go libraries write `New(addr string, opts ...Option)` instead of one parameter per setting?
basics
~20 sBecause callers pass only the settings they care about. Option is a function type such as func(*Client); each With... helper returns one, and New starts from a struct holding the package defaults and applies the options to it.
In Go, what does it mean for a type to have a useful zero value, and why do package authors design for one?
basics
~20 sA type has a useful zero value when var x T is ready to use with no constructor call. bytes.Buffer, sync.Mutex and http.Client all work this way, so callers can declare or embed them and start calling methods immediately.
In a Go Config struct, how do you tell a field the caller never set from one deliberately set to zero?
basics
~10 sMake the field a pointer, such as *int or *time.Duration. A nil pointer means the caller said nothing, so the constructor's default applies; a non-nil pointer to zero means they really asked for zero.
Why does a Go package usually export a blocking function instead of starting a goroutine for the caller?
basics
~20 sA caller can always add concurrency to a blocking function by starting it with go, but cannot remove concurrency the package started. Staying synchronous leaves the goroutine's lifetime, its errors and how many run in the caller's hands.
In Go, what makes an identifier visible outside its package, and what does an unexported struct field mean for importing packages?
basics
~20 sCase decides it. An identifier whose name begins with an upper-case letter is exported and visible to packages that import it; a lower-case one is package-private. Importing packages cannot read, set, or even name an unexported struct field.
Where do ctx context.Context and the error result belong in an exported Go function's signature?
basics
~20 sA context.Context parameter goes first and is named ctx; the error result goes last. Everything else sits between them, with any variadic parameter at the end, because that is the shape the standard library and every reviewer expect.
How does a Go service struct get its collaborators when main wires the program by hand?
basics
~20 sThrough its constructor's parameters. main creates each collaborator first and passes them to a function such as NewThumbnailer, which stores them in unexported struct fields, so the type never reaches outside itself for a dependency.
Why does adding a method to an exported Go interface break importers, when adding one to a struct does not?
basics
~20 sAn exported Go interface is satisfied implicitly, so any type with the right method set implements it. Adding a method enlarges that set, and every outside implementer stops compiling. A method on a struct affects only that type.
In Go, why do reviewers reject an exported interface declared beside its only implementation?
basics
~20 sGo checks interface satisfaction implicitly, so whoever needs the abstraction can declare it later. An interface written beside its only implementation adds indirection now, hides the concrete type's other methods and fields, and buys nothing.
Why does Go compile a call whose returned error you never check?
basics
~20 sGo's compiler rejects unused variables and unused imports, but not unused return values. A call like f.Close() is a complete statement on its own, so nothing forces you to look at the error it returns.
What is wrong with a package-level `var apiURL = os.Getenv("API_URL")` in a Go package?
basics
~20 sIt runs at package initialization, before main, so nothing can supply or check the value: the package cannot report a missing setting as an error, and a test can only change it by writing to a global.
Why is `defer f.Close()` on a file you just wrote an error-swallowing bug?
basics
~20 sA deferred call's return value is thrown away, so defer f.Close() discards the error reporting a failed write. Closing the file also does not flush a bufio.Writer above it, so buffered bytes are never written and the run still reports success.
What does an exported Go function lose by taking a parameter of type any?
basics
~20 sAn any parameter gives up the compile-time contract. The signature no longer says what is accepted, so the rule lives in a doc comment and a runtime type switch, and a caller's wrong type fails in production.