1. How are interfaces satisfied in Go?
Answer: Implicitly — a type satisfies an interface automatically if it implements all the methods the interface declares. There is no implements keyword.
Go’s interfaces are structural. You don’t declare “this type implements that interface.” Instead, the compiler checks: does the type have a method set that covers everything the interface requires? If yes, the type is assignable to the interface — at compile time for statically-known values, or at runtime for interface values.
type Writer interface { Write([]byte) (int, error) }
type File struct{}
func (File) Write(p []byte) (int, error) { return len(p), nil }
var w Writer = File{} // File satisfies Writer automatically
This is often called duck typing — “if it walks like a duck…” — but with static checking. The compiler verifies the method set rather than trusting runtime reflection.
Two consequences follow. First, a type can satisfy many unrelated interfaces, and interfaces can be composed (io.Reader + io.Writer → io.ReadWriter). Second, an empty interface (interface{} / any) is satisfied by every type, because it requires no methods.
The interview answer: Go interfaces are satisfied implicitly via structural typing — implement the methods, and the type fits the interface.
Answer:
Implicitly — a type satisfies an interface automatically if it implements all the methods the interface declares. There is no implements keyword.
Go’s interfaces are structural. You don’t declare “this type implements that interface.” Instead, the compiler checks: does the type have a method set that covers everything the interface requires? If yes, the type is assignable to the interface — at compile time for statically-known values, or at runtime for interface values.
type Writer interface { Write([]byte) (int, error) }
type File struct{}
func (File) Write(p []byte) (int, error) { return len(p), nil }
var w Writer = File{} // File satisfies Writer automatically
This is often called duck typing — “if it walks like a duck…” — but with static checking. The compiler verifies the method set rather than trusting runtime reflection.
Two consequences follow. First, a type can satisfy many unrelated interfaces, and interfaces can be composed (io.Reader + io.Writer → io.ReadWriter). Second, an empty interface (interface{} / any) is satisfied by every type, because it requires no methods.
The interview answer: Go interfaces are satisfied implicitly via structural typing — implement the methods, and the type fits the interface.
2. How does Go handle method receiver mechanics for value vs pointer receivers?
Answer: A value receiver works on a copy of the struct; a pointer receiver works on the original instance, so mutations persist.
When you define a method, you choose its receiver:
func (s Struct) Set(x int) // value receiver — s is a copy
func (s *Struct) Set(x int) // pointer receiver — s references the original
With a value receiver, Go passes a copy of the struct. Any field modification inside the method affects only that copy; the caller’s struct is untouched. Value receivers are appropriate for methods that only read state.
With a pointer receiver, the method gets the address of the original struct, so field assignments inside the method mutate the caller’s instance and persist after the call returns.
Two practical implications:
- Mutating methods must use pointer receivers.
- Method sets differ: pointer receivers make the method part of the
*Tmethod set (callable on bothTand*T), while value receivers are in theTmethod set. This matters when a type must satisfy an interface with pointer receivers — only a*Tvalue qualifies.
The interview answer: value receivers operate on a copy; pointer receivers operate on and mutate the original.
Answer:
A value receiver works on a copy of the struct; a pointer receiver works on the original instance, so mutations persist.
When you define a method, you choose its receiver:
func (s Struct) Set(x int) // value receiver — s is a copy
func (s *Struct) Set(x int) // pointer receiver — s references the original
With a value receiver, Go passes a copy of the struct. Any field modification inside the method affects only that copy; the caller’s struct is untouched. Value receivers are appropriate for methods that only read state.
With a pointer receiver, the method gets the address of the original struct, so field assignments inside the method mutate the caller’s instance and persist after the call returns.
Two practical implications:
- Mutating methods must use pointer receivers.
- Method sets differ: pointer receivers make the method part of the
*Tmethod set (callable on bothTand*T), while value receivers are in theTmethod set. This matters when a type must satisfy an interface with pointer receivers — only a*Tvalue qualifies.
The interview answer: value receivers operate on a copy; pointer receivers operate on and mutate the original.
3. How do you implement custom string formatting output for a struct type when using fmt.Println()?
Answer: Implement the fmt.Stringer interface by defining a String() string method.
fmt is interface-driven. When it formats a value, it checks whether the type satisfies Stringer — i.e., has a String() string method. If so, it calls it and prints the result.
type User struct { Name string }
func (u User) String() string {
return "User(" + u.Name + ")"
}
fmt.Println(User{"alice"}) // prints: User(alice)
Implementing String() gives you control over how %v, Println, Printf with %v, and error formatting display your type.
The interview answer: define func (t Type) String() string — satisfying fmt.Stringer — and fmt will use it automatically.
Answer:
Implement the fmt.Stringer interface by defining a String() string method.
fmt is interface-driven. When it formats a value, it checks whether the type satisfies Stringer — i.e., has a String() string method. If so, it calls it and prints the result.
type User struct { Name string }
func (u User) String() string {
return "User(" + u.Name + ")"
}
fmt.Println(User{"alice"}) // prints: User(alice)
Implementing String() gives you control over how %v, Println, Printf with %v, and error formatting display your type.
The interview answer: define func (t Type) String() string — satisfying fmt.Stringer — and fmt will use it automatically.
4. What is the correct syntax for an inline Type Switch in Go?
Answer:
switch v := i.(type) {
case int:
// ...
}
A type switch inspects an interface value’s dynamic type. The syntax is distinctive: the type assertion’s special form i.(type) — note type is a literal keyword here, not a variable name — and v is bound to the value typed as the matched concrete type.
func describe(i any) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("int %d", v) // v is int
case string:
return fmt.Sprintf("string %q", v) // v is string
default:
return "unknown"
}
}
Within each case, the bound variable v has the corresponding concrete type, so you can use it without casting. If you only care about the type, you can omit the binding: switch i.(type).
The interview answer: switch v := i.(type) { case T: ... } — with (type) as the literal type-assertion form.
Answer:
switch v := i.(type) {
case int:
// ...
}
A type switch inspects an interface value’s dynamic type. The syntax is distinctive: the type assertion’s special form i.(type) — note type is a literal keyword here, not a variable name — and v is bound to the value typed as the matched concrete type.
func describe(i any) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("int %d", v) // v is int
case string:
return fmt.Sprintf("string %q", v) // v is string
default:
return "unknown"
}
}
Within each case, the bound variable v has the corresponding concrete type, so you can use it without casting. If you only care about the type, you can omit the binding: switch i.(type).
The interview answer: switch v := i.(type) { case T: ... } — with (type) as the literal type-assertion form.
5. How do you implement method overriding in Go structs?
Answer: Go has no class inheritance, so there’s no override keyword. The idiomatic mechanism is struct embedding (composition): embed a struct, and the outer struct promotes the inner methods; declare a method with the same name to shadow it.
type Base struct{}
func (Base) Speak() string { return "base" }
type Child struct {
Base // embedding — promotion
}
func (Child) Speak() string { return "child" } // shadows Base.Speak
Child gets Base.Speak for free via promotion, but defines its own Speak, which wins when called on a Child. This gives a similar outcome to method overriding, but the model is composition, not inheritance:
- Promoted methods become part of the outer type’s method set.
- An outer method with the same name shadows the inner one (outer wins).
- There’s no virtual dispatch or
super; you reach the embedded method explicitly asc.Base.Speak().
The interview answer: Go uses struct embedding for composition, with same-named methods on the outer struct shadowing the embedded ones.
Answer:
Go has no class inheritance, so there’s no override keyword. The idiomatic mechanism is struct embedding (composition): embed a struct, and the outer struct promotes the inner methods; declare a method with the same name to shadow it.
type Base struct{}
func (Base) Speak() string { return "base" }
type Child struct {
Base // embedding — promotion
}
func (Child) Speak() string { return "child" } // shadows Base.Speak
Child gets Base.Speak for free via promotion, but defines its own Speak, which wins when called on a Child. This gives a similar outcome to method overriding, but the model is composition, not inheritance:
- Promoted methods become part of the outer type’s method set.
- An outer method with the same name shadows the inner one (outer wins).
- There’s no virtual dispatch or
super; you reach the embedded method explicitly asc.Base.Speak().
The interview answer: Go uses struct embedding for composition, with same-named methods on the outer struct shadowing the embedded ones.
6. What is the outcome of passing a struct to a function by value vs by pointer?
Answer: By value copies the entire struct contents onto the stack; by pointer passes just the address (8 bytes on 64-bit systems).
When you pass a struct by value, Go copies every field into the new parameter — for a large struct that’s a lot of copying and stack usage. The function works on its own copy; mutations don’t reach the caller.
When you pass a pointer (*Struct), the function receives the address — a single word (8 bytes on 64-bit platforms). No field copying. The function reads and mutates the original struct through the pointer.
The practical trade-off:
- Small structs — by value is fine and cache-friendly.
- Large structs — by pointer avoids the copy cost and allows mutation.
- Semantics — by value gives you an immutable-by-default view (the caller’s copy is untouched unless fields are mutable inside); by pointer is how you mutate the caller’s instance.
The interview answer: value copies the whole struct to the stack; pointer passes an 8-byte address, enabling mutation and avoiding the copy.
Answer:
By value copies the entire struct contents onto the stack; by pointer passes just the address (8 bytes on 64-bit systems).
When you pass a struct by value, Go copies every field into the new parameter — for a large struct that’s a lot of copying and stack usage. The function works on its own copy; mutations don’t reach the caller.
When you pass a pointer (*Struct), the function receives the address — a single word (8 bytes on 64-bit platforms). No field copying. The function reads and mutates the original struct through the pointer.
The practical trade-off:
- Small structs — by value is fine and cache-friendly.
- Large structs — by pointer avoids the copy cost and allows mutation.
- Semantics — by value gives you an immutable-by-default view (the caller’s copy is untouched unless fields are mutable inside); by pointer is how you mutate the caller’s instance.
The interview answer: value copies the whole struct to the stack; pointer passes an 8-byte address, enabling mutation and avoiding the copy.
Premium Content
Unlock Interfaces & Methods and all premium lessons with a subscription.
From ₹199.99/year — See plans