A pointer stores the memory address of a value. Use & to get the address of a value and * to dereference the pointer, which means reading or writing the value at that address.
Golang has pointers, but it does not allow pointer arithmetic. That keeps pointers useful for sharing and mutation while avoiding many low-level memory mistakes found in languages where pointer arithmetic is allowed.
| Symbol | Meaning |
|---|---|
&value | Get the address of value. |
*pointer | Dereference the pointer to access the value. |
*int | A pointer to an int. |
package main
import "fmt"
func main() {
score := 90
scorePointer := &score
fmt.Println(score) // 90
fmt.Println(scorePointer) // memory address
fmt.Println(*scorePointer) // 90
}
Pointers are useful when a function must modify the original value. Without a pointer, the function receives a copy, and changes inside the function do not affect the caller’s variable.
Passing a pointer still passes an argument by value. The copied argument is the address, so the function can use that address to reach and update the original value.
package main
import "fmt"
func activate(active *bool) {
*active = true
}
func main() {
userActive := false
activate(&userActive)
fmt.Println(userActive) // true
}
Golang passes arguments by value. If you pass an int, string, struct, slice header, map header, or pointer, the function receives a copy of that argument.
This rule explains why a function cannot mutate a caller’s struct fields unless it receives a pointer to the struct. It also explains why pointer receivers can mutate a struct while value receivers work on a copy.
type User struct {
Name string
}
func renameCopy(user User) {
user.Name = "Copy"
}
func renameOriginal(user *User) {
user.Name = "Original"
}
profile := User{Name: "Asha"}
renameCopy(profile)
fmt.Println(profile.Name) // Asha
renameOriginal(&profile)
fmt.Println(profile.Name) // Original
Methods use pointer receivers when they need to update the receiver or avoid copying a large struct. If some methods on a type require pointer receivers, many teams use pointer receivers consistently for that type.
Use value receivers for small immutable values, especially when the method only reads fields. Use pointer receivers for mutation, large structs, synchronization fields, or types that should not be copied.
| Receiver Type | Can Mutate Original? | Typical Use |
|---|---|---|
| Value receiver | No | Small read-only behavior |
| Pointer receiver | Yes | Mutation or avoiding large copies |
| Pointer receiver with mutex fields | Yes | Types containing locks should not be copied |
type Counter struct {
Value int
}
func (c *Counter) Increment() {
c.Value++
}
func (c Counter) IsZero() bool {
return c.Value == 0
}
counter := Counter{}
counter.Increment()
fmt.Println(counter.Value)
A pointer with no target is nil. Dereferencing nil causes a runtime panic, so optional pointer values should be checked before use.
Nil pointers are useful when absence is meaningful, such as an optional timestamp, an optional configuration value, or a linked structure where the next item may not exist.
func printName(name *string) {
if name == nil {
fmt.Println("name not provided")
return
}
fmt.Println(*name)
}
username := "Asha"
printName(&username)
printName(nil)
Slices and maps already contain internal references to underlying data. Passing a slice or map copies its header, but the copied header still points to shared backing data.
This means functions can often modify slice elements or map entries without receiving a pointer to the slice or map. You usually need a pointer to a slice only when the function must replace the slice header itself, such as appending and returning through the same parameter.
func markFirstDone(tasks []string) {
if len(tasks) > 0 {
tasks[0] = "done"
}
}
func addRole(roles map[string]bool, role string) {
roles[role] = true
}
tasks := []string{"pending"}
markFirstDone(tasks)
fmt.Println(tasks[0]) // done
Do not use pointers everywhere. Value types are often simpler and safer when no mutation or optional state is needed. Extra pointers can make ownership harder to understand.
Use pointers when they communicate a real need: mutate the original value, avoid copying a large struct, represent optional state, or satisfy an interface that requires pointer receiver methods.
& gets an address and * dereferences a pointer.
Explore 500+ free tutorials across 20+ languages and frameworks.