Structs
By the end of this lesson you'll model real-world things with your own struct types, give them behaviour with methods, change them safely with pointers, build bigger types by composition, and see how a type quietly satisfies an interface — the heart of idiomatic Go.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Defining a Struct and Making Instances
A struct (short for "structure") is a type you define to group related values together — like a Person with a Name and an Age . You declare the shape once with type Person struct { ... } , then create as many instances (individual values) as you like. You can fill the fields by name (clearest and safest) or by position (you must list them in the exact field order). Any field you leave out gets its type's zero value — 0 for numbers, "" for strings, false for bools.
Your turn. Define a small struct and build one instance using field names. Fill in the blanks marked ___ using the // 👉 hints, then run it and check the output.
2️⃣ Methods: Value vs Pointer Receivers
A method is a function attached to a type. You write it with a receiver in parentheses before the name: func (c Counter) Show() . The receiver type decides whether the method sees the original or a copy. A value receiver (c Counter) gets a copy — perfect for read-only methods, but any change is thrown away. A pointer receiver (c *Counter) works on the original , so changes stick. This value-vs-pointer choice is the single most common Go gotcha, so study both halves of this example.
3️⃣ Pointers: & and *
A pointer holds the memory address of a value rather than the value itself. Two operators do all the work: &x takes the address of x (giving a pointer), and *p dereferences the pointer (giving back the value, which you can read or overwrite). Passing a pointer into a function lets that function change your original value. Go is friendly here: it auto-dereferences struct pointers (write u.Name , not (*u).Name ) and forbids pointer arithmetic, so you can't accidentally walk off into invalid memory.
Your turn — fix a wallet that won't update. The Deposit method currently can't change the balance. Give it a pointer receiver and pass the right amount.
4️⃣ Embedding: Composition over Inheritance
Go has no classes and no inheritance. Instead you compose bigger types out of smaller ones by embedding : list a type inside a struct with no field name . The embedded type's fields and methods are then promoted — you can reach them directly on the outer struct. So an Employee that embeds User can use e.Name instead of e.User.Name , and inherits any methods User defines. This is how Go reuses code: build from parts rather than extending a base class.
5️⃣ A First Look at Interfaces
An interface is a named list of method signatures — a contract that says "any type with these methods qualifies." The big idea in Go: a type satisfies an interface automatically , just by having the right methods. There is no implements keyword and nothing to declare. Below, both Dog and Robot have a Speak() string method, so both are Speaker s — and one function can accept either. You'll go much deeper into interfaces in a later lesson; for now, just notice how loose and flexible this makes your code.
Common Errors (and the fix)
- My method ran but the struct didn't change. You used a value receiver, so the method edited a copy. Switch to a pointer receiver: func (w *Wallet) Deposit(...) . This is the #1 struct gotcha.
- "invalid operation: a == b (struct ... cannot be compared)". Structs are comparable with == only if every field is comparable. A struct containing a slice, map, or function can't be compared — compare the individual fields you care about instead.
- "panic: runtime error: invalid memory address or nil pointer dereference". You dereferenced a pointer that's nil (e.g. var u *User; u.Name ). Create the value first ( u := &User {} ) or guard with if u != nil { ... } .
- "too few values in Person {...} ". Positional literals must list every field in order. Either supply them all, or — better — switch to field names: Person { Name: "Al" } .
- "cannot use Robot ... as Speaker value: missing method Speak". The type doesn't have the exact method the interface needs. Check the method name, the receiver, and that the signature (parameters and return types) match precisely.
Pro Tips
- 💡 Prefer field names in struct literals — User { Name: "Al" } survives reordering and adding fields; positional literals break.
- 💡 Be consistent with receivers: if one method needs a pointer receiver, give them all pointer receivers so callers don't have to think about it.
- 💡 Keep interfaces small. "The bigger the interface, the weaker the abstraction." — Rob Pike. One or two methods is ideal.
- 💡 Print with %+v ( fmt.Printf("%+v", x) ) to see a struct's field names and values while debugging.
📋 Quick Reference
Task
Go Syntax
Define a struct
Instance by name
Instance positional
Value receiver (reads)
Pointer receiver (writes)
Address / dereference
Embedding
Interface
Frequently Asked Questions
Mini-Challenge: Rectangle Area
No blanks this time — just a brief and an outline. Define the struct and its method yourself, then run it and check your output against the expected line. This is exactly the shape of code real Go programs are built from.
🎉 Lesson Complete!
- ✅ A struct groups related fields; build instances by name or by position
- ✅ Omitted fields take their type's zero value ( 0 , "" , false )
- ✅ Value receivers read a copy; pointer receivers change the original
- ✅ & takes an address, * dereferences; Go has no pointer arithmetic
- ✅ Embedding composes types and promotes fields/methods — no inheritance
- ✅ A type satisfies an interface automatically just by having its methods
- ✅ Next lesson: Concurrency with Goroutines — Go's superpower for doing many things at once
Practice quiz
What is a struct in Go?
- A function with state
- A list of methods only
- A named type that groups related fields together
- A pointer alias
Answer: A named type that groups related fields together. A struct is a type that groups related fields under one name.
For type Person struct { Name string; Age int }, what is the zero value of var empty Person?
- {Name: "" Age: 0}
- nil
- {Name: nil Age: nil}
- a compile error
Answer: {Name: "" Age: 0}. Each field takes its type's zero value: "" for string, 0 for int.
What is the safest way to build a struct instance?
- Positional: Person{"Al", 30}
- var p Person only
- new(Person)
- By field name: Person{Name: "Al", Age: 30}
Answer: By field name: Person{Name: "Al", Age: 30}. Field-name literals are order-independent and survive added/reordered fields.
A value receiver method gets what?
- The original struct
- A copy of the struct, so changes are discarded
- A pointer to the struct
- Nothing
Answer: A copy of the struct, so changes are discarded. A value receiver operates on a copy; mutations don't affect the original.
Why might a method not change the struct it was called on?
- It used a value receiver instead of a pointer receiver
- The struct was nil
- Go forbids mutation
- The field was const
Answer: It used a value receiver instead of a pointer receiver. Value receivers work on a copy; use a pointer receiver (e.g. *Wallet) to make changes stick.
What does & do?
- Dereferences a pointer
- Allocates a slice
- Takes the address of a value, producing a pointer
- Compares two values
Answer: Takes the address of a value, producing a pointer. &x takes the address of x, producing a pointer.
What does * do to a pointer p?
- Takes its address
- Dereferences it to read or write the pointed-to value
- Doubles it
- Frees it
Answer: Dereferences it to read or write the pointed-to value. *p dereferences the pointer to reach the value it points at.
Does Go support pointer arithmetic like p++ or p + 4?
- Yes, freely
- Only for arrays
- Only with unsafe always required for structs
- No — Go forbids pointer arithmetic to keep memory safe
Answer: No — Go forbids pointer arithmetic to keep memory safe. Go has no pointer arithmetic, which keeps memory access safe.
How does a type satisfy an interface in Go?
- With an implements keyword
- Automatically, just by having the required methods (structural typing)
- By embedding the interface
- By registering it at runtime
Answer: Automatically, just by having the required methods (structural typing). Satisfaction is automatic: any type with the right methods satisfies the interface.
How does Go reuse code across struct types?
- Class inheritance
- Global mixins
- Embedding (composition over inheritance)
- Macros
Answer: Embedding (composition over inheritance). Go composes types via embedding rather than inheriting from base classes.
Continue this course
- Previous: Functions and Methods
- Next: Concurrency with Goroutines