December Code

Go structs and methods interview questions, with answers

Go has no classes. Data lives in structs, behaviour is attached to types through methods, and reuse comes from embedding one struct in another rather than from inheritance. Interviewers use that difference to check how you would model something in Go: when a method needs a pointer receiver, how an object gets constructed without constructors, what embedding promotes and what it does not, and how struct tags drive encoding.

The answers below take those questions in turn, each with code built and run on Go 1.27, from creating struct values to comparing them and using anonymous structs in table-driven tests. Then take the free Go diagnostic — ten questions across every Go topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.How do you define a struct and create values of it in Go?

    In short: Declare it with type Name struct { fields }, then create values with a composite literal, by field name or by position, or take a pointer with &Name{}.

    A struct type lists named, typed fields. A composite literal creates a value: User{Name: "Mei"} sets fields by name and leaves the rest at their zero values, which is the preferred form because it survives new fields being added, while User{"Raj", 30} sets every field by position. &User{...} creates the value and returns a pointer to it, and new(User) returns a pointer to a zero value. Fields are reached with a dot, and the dot also works through a pointer, so p.Age++ changes the struct p points to without writing (*p).Age. Fields and types starting with a capital letter are exported from the package.

    type User struct {
        Name string
        Age  int
    }
    
    func main() {
        u := User{Name: "Mei"}
        p := &User{"Raj", 30}
        p.Age++
        fmt.Println(u.Age, p.Age)
        // 0 31
    }
  2. 2.What is the difference between a value receiver and a pointer receiver in Go?

    In short: A value receiver gets a copy of the value, so it cannot change the caller's variable; a pointer receiver gets the address, so it can.

    A method's receiver is like a first parameter. With a value receiver, func (t Temp) F(), the method works on a copy, which is fine for reading. With a pointer receiver, func (t *Temp) Set(...), it receives a pointer and can modify the original, which Set needs. Go takes the address automatically when a pointer method is called on an addressable variable, so t.Set(100) works on a plain Temp. Use pointer receivers when a method modifies the receiver, when the struct is large enough that copying matters, or when it holds a mutex, and keep all of a type's methods on one kind of receiver for consistency. The choice also decides which interfaces the value type satisfies.

    type Temp struct{ C float64 }
    
    func (t Temp) F() float64 {
        return t.C*9/5 + 32
    }
    
    func (t *Temp) Set(c float64) {
        t.C = c
    }
    
    func main() {
        t := Temp{}
        t.Set(100)
        fmt.Println(t.F())
        // 212
    }
  3. 3.Does Go have constructors?

    In short: No: a type's zero value should be usable, and when setup is needed, the convention is a function named NewType that returns an initialised value.

    Go has no constructor syntax. The first design goal is a useful zero value, so that var s Stack is ready to use without any call. When a type needs setup, such as a preallocated buffer, validated settings or a started goroutine, the convention is an ordinary function named New plus the type name, returning a value or, more often, a pointer. Because it is just a function, it can return an error alongside the value, choose between implementations, or reuse cached values. Unexported fields, like items below, keep callers from building a half-initialised value by hand.

    type Stack struct {
        items []int
    }
    
    func NewStack(n int) *Stack {
        s := make([]int, 0, n)
        return &Stack{items: s}
    }
    
    func main() {
        s := NewStack(8)
        fmt.Println(len(s.items),
            cap(s.items))
        // 0 8
    }
  4. 4.What is struct embedding in Go, and how is it different from inheritance?

    In short: Embedding a type in a struct promotes the embedded type's fields and methods to the outer struct, but it is composition: the outer type is not a subtype of the inner one.

    Listing a type without a field name, as Logger inside Server below, embeds it. The embedded value's fields and methods are promoted, so s.Log("up") works as shorthand for s.Logger.Log("up"), and the embedded value is still reachable by its type name. That gives reuse without inheritance: a Server cannot be passed where a Logger is expected, there is no base class, and the embedded type knows nothing about the struct containing it. An outer type can declare its own method with the same name, which then takes precedence for calls on the outer type. Embedding also works with interfaces, both inside interface types and inside structs.

    type Logger struct {
        prefix string
    }
    
    func (l Logger) Log(m string) {
        fmt.Println(l.prefix + m)
    }
    
    type Server struct {
        Logger
        Port int
    }
    
    func main() {
        s := Server{
            Logger{"[srv] "}, 80}
        s.Log("up")
        // [srv] up
    }
  5. 5.What are struct tags in Go, and how does encoding/json use them?

    In short: Tags are string annotations after a field's type that libraries read through reflection; encoding/json uses them to set each field's JSON name and options.

    A struct tag is a raw string literal after a field, such as `json:"id"`, stored with the type and readable at run time through the reflect package. encoding/json looks for the json key: it renames the field in the output, and options such as omitempty skip zero values, while json:"-" skips the field entirely. Only exported fields, those starting with a capital letter, are encoded at all, which is why the unexported note field below is missing from the output even without a tag. Other libraries use their own keys in the same string, such as db for SQL mappers or validate for validators.

    type Item struct {
        ID   int    `json:"id"`
        Name string `json:"name"`
        note string
    }
    
    func main() {
        b, _ := json.Marshal(
            Item{7, "pen", "x"})
        fmt.Println(string(b))
        // {"id":7,"name":"pen"}
    }
  6. 6.Can you compare Go structs with ==?

    In short: Yes, if every field is comparable; == then compares field by field. A struct with a slice, map or function field cannot be compared, and that is a compile error.

    Struct equality is defined field by field, so two values of the same struct type are equal when all their fields are. That only works when every field's type is comparable. A slice, map or function field makes the whole struct incomparable, and writing a == b is rejected at compile time, as below, not left to fail at run time. Such structs can still be compared with reflect.DeepEqual, which walks the values recursively, or with a hand-written Equal method, which is usually faster and states what equality should mean. Comparable structs can also be used as map keys.

    type Pair struct {
        K string
        V []int
    }
    
    func main() {
        a, b := Pair{}, Pair{}
        fmt.Println(a == b)
        // does not compile
        eq := reflect.DeepEqual
        fmt.Println(eq(a, b))
        // true
    }
  7. 7.What are anonymous structs used for in Go?

    In short: They are struct types written inline without a name, used for one-off data such as the cases of a table-driven test or a small JSON payload.

    A struct type can be written directly where it is used, without a type declaration. The most common place is a table-driven test: a slice of anonymous structs lists the inputs and expected outputs, and one loop runs every case, so adding a case is one line. They also suit one-off values such as a configuration read once or the shape of a JSON response used in a single function. Because the type has no name, it cannot have methods, and repeating the same anonymous struct in several places is a sign that it should become a named type.

    tests := []struct {
        in   int
        want bool
    }{
        {4, true},
        {7, false},
    }
    for _, tc := range tests {
        got := tc.in%2 == 0
        ok := got == tc.want
        fmt.Println(ok)
    }
    // true
    // true

How the diagnostic asks it

One question from the Go bank, exactly as a sitting would show it. The bank has 3 on structs & methods and 30 across Go.

Structs & Methods · easyGO-016

What does this Go code print?

type P struct{ X, Y int }
a := P{1, 2}
b := P{1, 2}
fmt.Println(a == b)
  1. 1false, because a and b are different variables
  2. 2truecorrect
  3. 3It does not compile: structs cannot be compared with ==
  4. 4It panics at run time

A struct is comparable when all of its fields are, and == then compares the values field by field. a and b are two separate variables, but both have X 1 and Y 2, so a == b is true. False describes comparing two pointers, &a == &b, which compares addresses. Structs can be compared as long as every field can: a struct containing a slice, a map or a function cannot be compared with ==, and that is a compile-time error, not a panic. Comparable structs can also be map keys, which is a common way to key a map by two values at once.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 Go questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated