Go Module 5
Structs, pointers and methods interview questions
Interview questions on structs, pointers and methods: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What is the difference between a keyed and a positional struct literal?
Answered in the lesson Structs and struct literals, including Go 1.27
-
Fresher
What does new return, and how does new(29) differ from new(int)?
-
Fresher
What is a method receiver in Go?
-
Fresher
Is embedding in Go the same as inheritance?
Answered in the lesson Embedding: composition instead of inheritance
-
Fresher
Name two standard-library types whose zero value is ready to use, and say what that means.
Answered in the lesson Constructors, useful zero values and options
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
Your team upgrades a shared package, and three services stop compiling with too few values in struct literal of type rail.Seat. The package only added a field to Seat. Explain why, how you fix the services, and how you keep it from happening again.
- What it tests
- Knowing that positional literals list every field while keyed ones survive new fields, what go vet's composites check reports, and how a package can be designed to grow.
- First step
- Find the failing literals: each lists Seat's fields by position, rail.Seat{"B2", 34}, so a third field leaves it one value short. Rewrite each with keys, rail.Seat{Coach: "B2", Berth: 34}, which compiles whatever fields Seat gains, and run go vet on every service to find the positional literals that have not broken yet.
- Follow-ups
-
- Why does go vet report positional literals of imported types only?
- Can the package's author make such literals impossible?
- Is a new field always harmless for keyed literals?
From the lesson Structs and struct literals, including Go 1.27
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A booking service must spot duplicate bookings: the same passenger, train, travel day and berth. A teammate keys a map by one string joined from the four values with a | between them. Would you use a struct key instead? Design the key and say what can go wrong with each choice.
- What it tests
- Knowing which types are comparable, struct keys against joined string keys, separator collisions, normalising values before building a key, and which field types make a poor key.
- First step
- List the values that identify a booking and check that each is comparable: strings and integers are, slices are not. A struct of them, bookingKey{Passenger, Train string; Day, Berth int}, is a valid key that compares field by field, so it cannot collide the way joined text can, provided every value is normalised the same way first.
- Follow-ups
-
- How can a joined string key report a false duplicate?
- What goes wrong if one field is a float64 or a pointer?
- What does each kind of key cost in memory?
From the lesson Structs and struct literals, including Go 1.27
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A service keeps 20 million fare records in memory, each a struct of a bool, an int64, a bool, an int32 and a byte, in that order. Memory use is higher than the fields suggest. How do you measure what the struct costs, what do you change, and what do you check before changing it?
- What it tests
- Measuring size and offsets with package unsafe, explaining alignment padding, estimating the saving at scale, and weighing it against readability and the tools that suggest an order.
- First step
- Measure before changing anything: unsafe.Sizeof and unsafe.Offsetof show 32 bytes per record, 17 of them padding, because the int64 must start at a multiple of 8. With the int64 first and the small fields together the same fields take 16 bytes, so 20 million records shrink from 640 MB to 320 MB.
- Follow-ups
-
- Why is the struct 32 bytes when its fields add up to 15?
- What else could hold the memory besides padding?
- When would you leave the order alone?
From the lesson Structs and struct literals, including Go 1.27
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
In a code review, a teammate asks you to change func NewOrder(id string) *Order to return an Order, because returning pointers is slow. How do you decide, and what do you measure before changing it?
- What it tests
- Knowing that escape analysis decides heap allocation, measuring allocations and their cost, and weighing copying, identity and the conventions of an API.
- First step
- Separate the claim from the evidence. Returning a pointer to a new Order does make the Order a heap allocation, while returning the value lets the caller keep it where it likes; whether that matters depends on how often NewOrder runs and on what the caller does with the result, which a measurement shows and a rule does not.
- Follow-ups
-
- How do you see what the compiler decided?
- What does the value version cost instead?
- When is a pointer the right result whatever it costs?
From the lesson Pointers, new and escape analysis
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After a Go toolchain upgrade, a service that ran for years starts to panic with a nil pointer dereference in a function that opens a file and reads its name. Nobody changed the function. How do you investigate, and what do you fix?
- What it tests
- Reading a panic's stack trace, knowing that a failed call may return nil results with its error, checking errors before using results, and how a compiler fix can expose an old bug.
- First step
- Read the stack trace to find the line that panicked, then look at the order of the statements around it: which value is nil on that line, which call produced it, and whether that call's error is checked before or after the line.
- Follow-ups
-
- Why did the same code not panic before the upgrade?
- How do you find other code with the same shape?
- Should a recover keep the service running instead?
From the lesson Pointers, new and escape analysis
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A Wallet type has Credit and Debit methods. Its tests check only the errors the methods return, and they pass, but in production a balance never goes down after a debit. Debit is declared as func (w Wallet) Debit(amount int) error. Explain the bug, and how you keep it from coming back.
- What it tests
- Knowing that a value receiver works on a copy, why the compiler accepts it, writing tests that check state, and keeping the receivers of a type consistent.
- First step
- Read Debit's receiver: (w Wallet) is a copy, so the method subtracts from the copy and returns, and the caller's wallet keeps its balance. Then read what the tests assert: the error, which the copy computes correctly, but never the balance after the call.
- Follow-ups
-
- Why does the compiler accept the method?
- Which test would have caught it?
- How do you keep a type's receivers consistent?
From the lesson Methods: value and pointer receivers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A teammate puts Account values in a []Depositor and gets: cannot use acc as Depositor value, method Deposit has pointer receiver. They propose giving Deposit a value receiver so that it compiles. What do you tell them, and what do you change instead?
- What it tests
- Knowing the method sets of T and *T, why an interface cannot call a pointer method on a value it holds, and choosing between values and pointers in a collection.
- First step
- Ask what Deposit has to do. It changes the balance, so a value receiver would make the code compile and silently stop the deposits from arriving. The compiler is pointing at the real decision: whether the slice should hold the accounts themselves or pointers to them.
- Follow-ups
-
- Why can an Account value not be a Depositor?
- What changes when the slice holds *Account?
- Where else does Go refuse to take an address for you?
From the lesson Methods: value and pointer receivers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A request counter holds a sync.Mutex and an int, and its Inc method has a value receiver. Handlers call c.Inc() on a package-level counter, yet the total it reports stays at 0. Explain why, fix it, and say how a tool could have caught it before release.
- What it tests
- Knowing that a value receiver copies the whole struct, mutex included, the rule against copying a Mutex, go vet's copylocks check, and pointer receivers for types that hold a lock.
- First step
- Look at Inc's receiver. With (c Counter), every call copies the counter, its mutex and its int, increments the copy, and drops the copy when Inc returns, so the package-level counter is never written and still reports 0.
- Follow-ups
-
- Why is a copied mutex dangerous even when the copy is the one used?
- What does go vet report, and where?
- How do you stop the type from being copied at all?
From the lesson Methods: value and pointer receivers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A team ports a Java report generator to Go. BaseReport has a Render method that calls r.Header(), and SalesReport embeds BaseReport and declares its own Header. Every sales report renders the base header. Explain why, and redesign it the Go way.
- What it tests
- Knowing that a promoted method resolves its calls on the inner type, that only interfaces dispatch at run time, and turning a template method into an interface or into parts.
- First step
- Look at where Render is declared: on BaseReport, so its receiver is the BaseReport inside SalesReport, and the r.Header() inside it was resolved on BaseReport. Then ask which part of Render really varies from report to report; that part is what the redesign has to pass in.
- Follow-ups
-
- Why does Go not dispatch here as Java does?
- What does the redesigned Render look like?
- When is embedding BaseReport still useful?
From the lesson Embedding: composition instead of inheritance
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A shared Cache type embeds sync.Mutex so that its methods can call c.Lock(). Months later a production deadlock is traced to a caller in another package that called Lock on a Cache and never released it. What made that possible, and how do you redesign the type?
- What it tests
- Knowing that embedding adds the embedded type's methods to the outer type's API, keeping a lock unexported, and the receivers a type with a mutex needs.
- First step
- List what a caller in another package can call on a *Cache: its own exported methods, and every method promoted from its embedded fields. Then decide which of those a caller should be able to call at all, and which only the Cache's own methods should.
- Follow-ups
-
- Why could the other package call Lock at all?
- What should the redesigned type expose?
- Which receivers must its methods have?
From the lesson Embedding: composition instead of inheritance
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A middleware wraps http.ResponseWriter in a struct that embeds it and counts bytes in its own Write. In one code path the wrapper is built with a nil writer and the handler panics, and streaming endpoints behind the middleware stop flushing. Explain both problems and how you build the wrapper safely.
- What it tests
- Knowing what an embedded interface delegates to, why a nil one panics, that a wrapper has only the methods it embeds or declares, and how Go's HTTP package expects wrappers to expose the original writer.
- First step
- Treat the two failures separately. For the panic, ask what the embedded field holds in the code path that fails. For flushing, compare what the streaming code asks of its writer with what the wrapper itself offers, method by method, and check where each of those methods comes from.
- Follow-ups
-
- Why does the wrapper lose Flush when the writer inside has it?
- How do you keep Flush and the other optional methods working?
- Where should the nil check go?
From the lesson Embedding: composition instead of inheritance
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
You are designing NewClient for a library that 40 services use. It has one required setting, the base URL, and nine optional ones with defaults; one of them, retries, must accept 0. Do you use functional options, a Config struct or something else? Defend the design.
- What it tests
- Separating required from optional settings, the zero-versus-unset problem of config structs, how each design copes with change, checking rules that span settings, and returning a concrete type.
- First step
- Sort the settings first. The base URL has no default, so it is a parameter that the compiler makes every caller pass. Then look at the nine optional ones and ask which can legitimately be 0, because that decides whether a plain struct field can carry them.
- Follow-ups
-
- How does each design cope with a tenth setting next year?
- Where do you check a rule that involves two settings?
- Should NewClient return an interface?
From the lesson Constructors, useful zero values and options
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
During a payment provider's slowdown, a service's goroutine count climbs to 80,000 and it runs out of memory, although its handlers normally finish in milliseconds. The outgoing calls use http.Get. What happened, and how do you change the code and the team's defaults?
- What it tests
- Knowing which client http.Get uses and what its zero Timeout means, how blocked calls hold goroutines, and designing defaults that fail safe.
- First step
- Ask what bounds the duration of each outgoing call. Find which client http.Get uses and what its Timeout is, then work out how long a handler can wait when the provider slows down, and what each waiting handler holds while it waits.
- Follow-ups
-
- Why did the goroutine count grow without limit?
- Which timeout do you choose, and where do you set it?
- How do you design your own types so that a forgotten setting fails safe?
From the lesson Constructors, useful zero values and options
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Two teams share a package whose constructor reads an exported variable, DefaultRetries. After one team sets it to 10 in an init function, the other team's service starts sending a payment request up to ten times. Explain what happened and redesign the package.
- What it tests
- Knowing that a package-level variable is one variable for the whole program, how initialisation order hides the cause, why constructors should not read mutable globals, and options or parameters as the fix.
- First step
- Trace where the other team's clients get their retry count. The constructor reads DefaultRetries when it runs, and there is one DefaultRetries for the whole program, so an assignment anywhere, in any package's init function, changes what every later call of the constructor builds.
- Follow-ups
-
- Why is the problem hard to find from the other team's code?
- What should the package look like instead?
- How do tests make the same mistake worse?
From the lesson Constructors, useful zero values and options
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes