Pointers
Every variable lives somewhere in the computer's memory, and that place has an address. A pointer is a value that holds such an address. With a pointer, a function can change a variable that belongs to its caller, and big values can be shared without copying.
Pointers scare many students who learned them in C. Go's pointers are much safer: there is no pointer arithmetic, memory is freed automatically, and the compiler handles the tricky parts. In this lesson you will learn the two pointer operators, use pointers with functions and structs, and know when not to use them.
& and *
Two operators do all the work:
&xgives the address of the variablex. Read it as "address of x".*pfollows the pointerpto the value it points at. Read it as "the value at p".
The type of a pointer to an int is written *int.
package main
import "fmt"
func main() {
score := 72
p := &score // p holds the address of score
fmt.Println(*p)
fmt.Printf("%T\n", p)
*p = 85 // change score through the pointer
fmt.Println(score)
fmt.Println(p == &score)
}72 *int 85 true
p and &score hold the same address, so writing to *p changed score itself. If you print p directly you will see an address such as 0xc000012345; it differs on every run, so do not depend on it.
Changing the caller's variable
Go passes arguments by value: a function gets a copy. To let a function change the caller's variable, pass a pointer:
package main
import "fmt"
func addGraceCopy(marks int) {
marks += 5 // changes only the local copy
}
func addGrace(marks *int) {
*marks += 5 // changes the caller's variable
}
func main() {
rohan := 35
addGraceCopy(rohan)
fmt.Println("After copy version:", rohan)
addGrace(&rohan)
fmt.Println("After pointer version:", rohan)
}After copy version: 35 After pointer version: 40
The caller writes &rohan to hand over the address. The *int in the function signature tells every reader "this function may change your value".
nil pointers and optional values
The zero value of a pointer is nil: it points at nothing. Following a nil pointer stops the program with panic: runtime error: invalid memory address or nil pointer dereference. Always check for nil when a pointer may be empty.
That "maybe empty" property is useful. A pointer can say "this value was not given", which is different from "the value is zero":
package main
import "fmt"
func describe(discount *int) string {
if discount == nil {
return "no discount set"
}
return fmt.Sprintf("discount %d%%", *discount)
}
func main() {
var notSet *int
fmt.Println(notSet == nil, describe(notSet))
zero := 0
fmt.Println(describe(&zero))
fmt.Println(describe(new(15)))
}true no discount set discount 0% discount 15%
The last line uses new. The built-in new(T) creates a variable of type T with its zero value and returns a pointer to it. Since Go 1.26, new also accepts an expression: new(15) creates an int variable holding 15 and returns its address, so you no longer need a temporary variable just to take its address.
Pointers to structs
You will learn structs fully in the next lesson; for now, a struct is a type that groups named fields. Pointers to structs are very common, and Go lets you write p.Field instead of the longer (*p).Field:
package main
import "fmt"
type Account struct {
Holder string
Balance int
}
func deposit(a *Account, amount int) {
a.Balance += amount // Go reads this as (*a).Balance
}
func main() {
acc := Account{Holder: "Farhan", Balance: 1000}
deposit(&acc, 500)
fmt.Println(acc.Holder, acc.Balance)
same := &acc
same.Balance -= 200
fmt.Println(acc.Balance)
}Farhan 1500 1300
Returning a pointer to a local variable
In C, returning the address of a local variable is a serious bug. In Go it is completely safe. The compiler notices that the variable is still needed after the function returns and keeps it alive (it "escapes to the heap"):
package main
import "fmt"
func newCounter() *int {
n := 0
return &n
}
func main() {
c := newCounter()
*c++
*c++
fmt.Println(*c)
}2
*c++ means "increase the value that c points at". The garbage collector frees n once nothing points at it any more.
When to use pointers
Use a pointer when:
- a function must change the caller's value;
- a struct is large and copying it on every call would be wasteful;
- you need "no value" (
nil) as a possible state; - a type has methods that change it (pointer receivers, next lesson).
Do not use pointers to slices or maps just to share them. A slice already refers to a shared array, and a map already refers to shared data, so a function can change their elements without a pointer. The exception is when the function changes the slice's length with append: then return the new slice, as append itself does.
Common mistakes
- Dereferencing a nil pointer. Check
if p != nilbefore*pwhen the pointer may be empty. - Forgetting
&at the call. Passingrohanwhere*intis expected is a compile error; write&rohan. - Using pointers everywhere "for speed". Small values like
intor small structs are cheap to copy, and copies are easier to reason about. - Appending inside a function and expecting the caller's slice to grow. Return the new slice instead.
Key points
&xtakes the address ofx;*preads or writes the value at that address;*Tis the pointer type.- Go passes arguments by value; pass a pointer to let a function change the caller's variable.
- A nil pointer points at nothing; dereferencing it panics.
new(T)gives a pointer to a zero value; Go 1.26new(expr)gives a pointer to that value.- Struct fields work through pointers with
p.Field, and returning a pointer to a local is safe.
Exercise
Write a function swap(a, b *int) that exchanges two integers through their pointers, and a function applyDiscount(price *int, percent int) that reduces a price in place. In main, swap Priya's and Meera's seat numbers (12 and 31) and apply a 20% discount to a ₹1,500 ticket, printing the results.
Show solution
package main
import "fmt"
func swap(a, b *int) {
*a, *b = *b, *a
}
func applyDiscount(price *int, percent int) {
*price -= *price * percent / 100
}
func main() {
priya, meera := 12, 31
swap(&priya, &meera)
fmt.Println("Priya:", priya, "Meera:", meera)
ticket := 1500
applyDiscount(&ticket, 20)
fmt.Println("Ticket:", ticket)
}Priya: 31 Meera: 12 Ticket: 1200
Both functions receive addresses, so their changes land in main's variables. 20% of 1,500 is 300, leaving 1,200.
Quick Quiz
1. What does &x give you?
& is the address-of operator.
2. After n := 5; p := &n; *p = 9, what is n?
Writing through the pointer changes the variable it points at.
3. What happens when you dereference a nil pointer?
Following a nil pointer causes a "nil pointer dereference" panic.
4. What does new(15) return in Go 1.26 and later?
Since Go 1.26, new accepts an expression and returns a pointer to a variable initialised with it.
Learn Go with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
