ProgrammingSenior Go Developer

How does value semantic work for structs in Go, and what surprises arise when passing structs and their slices?

Pass interviews with Hintsage AI assistant

Answer.

Background:

Go was designed as a language with explicit value semantics: almost everything is copied by value when passed, including structs, but not pointers and slices. This simplifies reasoning and enhances safety, but introduces several "traps".

Issue:

Developers often expect that modifications to a struct passed to a function will be visible externally. However, the entire content (including nested fields — by value!) is copied. Slices and maps have different behavior, where the "container" is copied, but not the "content".

Solution:

Pass large structs by pointer if modification is expected. For slices, only the descriptor (length, capacity, pointer) is copied, not the content — changes to the original slice (via index) will be visible externally. For structs — everything is copied:

type Point struct { X, Y int } func move(p Point) { p.X = 100 } func movePtr(p *Point) { p.X = 100 } func demo() { pt := Point{10, 10} move(pt) fmt.Println(pt.X) // 10 movePtr(&pt) fmt.Println(pt.X) // 100 }

Key Features:

  • A struct is always copied by value when passed, unless it is a pointer.
  • For slices and maps, only the "head" (descriptor) is copied.
  • Proper management of passing by pointer or by value is key to code predictability.

Tricky Questions.

If a struct is changed inside a function, will the original change?

No, if the struct is passed by value — changes are local.

type User struct {Name string} func f(u User) {u.Name = "Ann"}

If an element of a slice is changed inside a function, will the original change?

Yes. Slices are a "view" on a shared array. Changing an element modifies the original data.

func f(s []int) {s[0] = 99}

What happens if a slice created inside a function is returned?

The "header" of the slice is copied, but the underlying array remains accessible. If no reference is saved externally, the data may be collected by GC.

Common Errors and Anti-Patterns

  • Implicitly passing structs by value when a reference was expected.
  • Expecting modifiable-reference semantics, as in other languages.
  • Errors when working with slices (e.g., forgetting the underlying array is shared).

Real-Life Example

Negative Case

In a function, processing the User struct was done by value — changes did not propagate back, making bugs hard to trace.

Pros:

  • Safe — does not modify the original (if this is desired).

Cons:

  • Non-obvious bug: the function does not modify the original.

Positive Case

Large structs are explicitly passed by pointer, and behavior regarding slices is always commented or checked. There is no confusion, everyone expects value semantics.

Pros:

  • Predictability, ease of maintenance.
  • Safer.

Cons:

  • Requires attentiveness, often explicit pointers for mutability.