Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Pointers and Structs
Go has pointers, but no pointer arithmetic. Structs are the way to create complex data types.Pointers
A pointer holds the memory address of a value. Think of it like a house address written on a slip of paper: the paper itself is not the house, but it tells you exactly where to find the house. When you pass a pointer to a function, you are handing over the address, not a copy of the entire house — the function can then go to that address and modify what is there. Key Syntax:- The type
*Tis a pointer to aTvalue. Its zero value isnil. - The
&operator generates a pointer to its operand (“address of”). - The
*operator denotes the pointer’s underlying value (“dereferencing”).
- Modify Function Arguments: Pass a pointer to allow a function to modify the original value.
- Avoid Copying Large Structs: Passing a pointer is more efficient than copying a large struct.
- Share Data: Multiple parts of your program can reference the same data.
p++ to move to the next memory address. This prevents common memory bugs.
Structs
A struct is a collection of fields.Pointers to Structs
Struct fields can be accessed through a struct pointer.X of a struct when we have the struct pointer p we could write (*p).X. However, that notation is cumbersome, so the language permits us instead to write just p.X, without the explicit dereference.
Struct Literals
You can list just a subset of fields by using theName: syntax. (And the order of named fields is irrelevant.)
Methods
Go does not have classes. However, you can define methods on types. A method is a function with a special receiver argument.Pointer Receivers
You can declare methods with pointer receivers. This means the receiver type has the literal syntax*T for some type T. (Also, T cannot itself be a pointer such as *int.)
Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
Choosing a Value or Pointer Receiver
There are two reasons to use a pointer receiver:- Modify the receiver: The method needs to modify the value that the receiver points to.
- Performance: To avoid copying the value on each method call. This is more efficient if the struct is large.
Struct Tags
Struct tags are metadata strings attached to struct fields, commonly used for serialization, validation, and database mapping. They are one of Go’s most practical features for real-world development:json:"-" tag is particularly important — it prevents sensitive fields like passwords from accidentally leaking in API responses. A senior engineer would say: “always tag your struct fields for JSON, and always use json:\"-\" for sensitive data.”
Interview Deep-Dive
When should you use a pointer receiver versus a value receiver on a method? What happens if you mix them on the same type?
When should you use a pointer receiver versus a value receiver on a method? What happens if you mix them on the same type?
Strong Answer:
- Use a pointer receiver when: the method needs to mutate the receiver, the receiver is a large struct and you want to avoid copying it, or consistency demands it because other methods on the type already use pointer receivers. Use a value receiver when: the type is small and immutable (like
time.Time, a coordinate pair, or a color value), and the method is a pure computation with no side effects. - The idiomatic rule is: if any method on a type needs a pointer receiver, make ALL methods use pointer receivers. Mixing creates confusion about whether the type should be used as a value or pointer, and it has concrete consequences for interface satisfaction.
- Specifically, a
*Tcan call both pointer-receiver and value-receiver methods (the compiler automatically takes the address of a value or dereferences a pointer as needed). But a plainTvalue can only call value-receiver methods through an interface. So if you havetype Shape interface { Area() float64; Scale(f float64) }, andScaleuses a pointer receiver whileAreauses a value receiver, then*CirclesatisfiesShapebutCircledoes not. This mismatch produces the infamous “does not implement interface” error. - In production, I default to pointer receivers for almost everything. The exceptions are genuinely small value types (under 64 bytes), types that should be immutable by design, and types used as map keys (which must be comparable).
unsafe package which allows you to convert between pointer types, do pointer arithmetic via unsafe.Add, and bypass type safety entirely. The unsafe package is explicitly not covered by Go’s compatibility guarantee — code using it may break between Go versions. In production, you should almost never use unsafe directly. The legitimate uses are in low-level libraries doing memory-mapped I/O, FFI with C code, or performance-critical serialization. If you see unsafe in application code during a code review, that is a red flag.Explain struct embedding in Go. How is it different from inheritance, and what happens when two embedded types have a method with the same name?
Explain struct embedding in Go. How is it different from inheritance, and what happens when two embedded types have a method with the same name?
Strong Answer:
- Struct embedding is Go’s version of composition. When you embed a type inside a struct (by listing it without a field name), all of the embedded type’s methods and fields are “promoted” to the outer struct. You can call them directly on the outer struct without qualifying through the field name. But this is not inheritance — there is no “is-a” relationship. The outer struct contains the embedded type as a field, and the compiler syntactically rewrites
outer.Method()toouter.EmbeddedField.Method(). - When two embedded types have a method with the same name, Go does not pick one — it reports an ambiguity error at compile time if you try to call the method on the outer struct without qualifying which embedded type you mean. You must explicitly write
outer.TypeA.Method()orouter.TypeB.Method(). You can also define a method directly on the outer struct with the same name, which shadows both embedded methods and resolves the ambiguity. - This is fundamentally different from inheritance. In Java, if class C extends B extends A, and B overrides a method from A, polymorphism means calling the method on a C instance calls B’s version. In Go embedding, there is no virtual dispatch — the promoted method still operates on the embedded type, not the outer type. The embedded type does not “know” about the outer type.
- A practical example: embedding
sync.Mutexin a struct gives the structLock()andUnlock()methods directly. But be careful with exported embedding — if you embed async.Mutexin an exported struct, the Lock/Unlock methods become part of your public API, which you probably do not intend. The convention is to embed unexported types or use a named field for types you do not want to expose.
json:"email" tag on a field. What happens if you embed another struct that also has an email JSON tag?JSON marshaling follows the same promotion rules as method promotion. If the outer struct has a field with the same JSON tag as an embedded struct’s field, the outer field takes precedence (it shadows the embedded one). If two embedded structs both have a field with the same JSON tag and there is no shadowing field on the outer struct, json.Marshal silently omits the ambiguous field rather than picking one or erroring. This is a subtle source of bugs — a field can “disappear” from your JSON output because of an embedding conflict, and you get no compiler warning. The fix is to be explicit: either shadow the ambiguous field on the outer struct or use named fields instead of embedding.You are designing a Go API where users create a Config struct with 15 optional fields. Compare the approaches: exported fields with zero values, a constructor with a config struct parameter, and the functional options pattern.
You are designing a Go API where users create a Config struct with 15 optional fields. Compare the approaches: exported fields with zero values, a constructor with a config struct parameter, and the functional options pattern.
Strong Answer:
- Exported fields with zero values is the simplest approach:
cfg := Config{Timeout: 5*time.Second}. The zero value of unset fields serves as the default. This works when zero values are meaningful defaults (0 for integers, "" for strings, false for booleans). The downside is that you cannot distinguish “user set this to zero” from “user did not set this” — ifPort: 0means “use default” but someone actually wants port 0, you have a problem. Validation is also deferred to runtime. - A constructor with a config struct is cleaner for required validation:
NewServer(Config{Host: "0.0.0.0", Port: 8080}). You can validate required fields in the constructor and return an error. The struct can use pointer fields for optional values (Port *int) to distinguish “not set” from “set to zero.” Downside: the config struct can become unwieldy with many fields, and callers must construct the whole struct even to override one setting. - The functional options pattern (
NewServer(WithHost("0.0.0.0"), WithPort(8080))) is the most flexible. Each option is a function that modifies the config. Options can validate their own values, compose freely, and can be stored in variables for reuse. The constructor sets defaults first, then applies options. This is the pattern used bygrpc.NewServer,zap.New, and many other production Go libraries. - My recommendation: use exported fields for simple structs with fewer than 5 fields. Use functional options when you have many optional settings, need validation per option, or the API is a library consumed by other teams. The functional options pattern has become the de facto standard for configurable constructors in the Go ecosystem.
json:"-" tag, and tell me about a production incident that could happen if you forget it?The json:"-" tag tells the JSON encoder to skip that field entirely. Without it, fields like Password, InternalToken, or SecretKey would be included in any JSON response that marshals the struct. I have seen a real incident where a user profile endpoint accidentally returned hashed passwords because the User struct was marshaled directly without a separate DTO, and the password field lacked the - tag. The passwords were bcrypt hashes, so they were not immediately exploitable, but it was still a security incident requiring user notification. The defense is defense in depth: always use json:"-" on sensitive fields, use separate response DTOs that only include fields meant for the client, and add integration tests that verify sensitive fields are absent from API responses.