Skip to content

Go Pointers

Pointers are a fundamental concept in Go, allowing you to work directly with memory addresses. While Go manages memory automatically (garbage collection), pointers are essential for certain tasks like:

  • Modifying function arguments directly (pass-by-reference semantics).
  • Efficiently passing large data structures to functions without copying.
  • Working with certain standard library APIs (e.g., methods with pointer receivers).
  • Representing optional or nullable values (a nil pointer).

Every variable in Go resides at a specific memory location, identified by its address. You can obtain the memory address of a variable using the address-of operator (&).

package main
import "fmt"
func main() {
a := 10
fmt.Printf("Value of 'a': %d\n", a)
// Use %p format specifier to print the memory address in hexadecimal
fmt.Printf("Memory address of 'a': %p\n", &a)
}

When executed, this might produce:

Value of 'a': 10
Memory address of 'a': 0xc000018030

(The specific address will vary each time you run the program).

A pointer is a special type of variable that stores the memory address of another variable. It ‘points’ to the location where the actual data is stored.

To declare a pointer variable, you use the asterisk (*) before the type name. The zero value for a pointer is nil.

// General syntax: var pointerName *DataType
var ip *int // ip is a pointer to an integer
var fp *float64 // fp is a pointer to a float64
var sp *string // sp is a pointer to a string

The type *int means “pointer to an integer”. The pointer itself (ip) holds a memory address, while the location it points to is expected to contain an int value.

Working with pointers involves three main steps:

  1. Declare a pointer variable (e.g., var ip *int).
  2. Assign the memory address of a variable to the pointer using the & operator (e.g., ip = &a).
  3. Access the value stored at the pointer’s address using the dereference operator (*) (e.g., value := *ip).

Example:

package main
import "fmt"
func main() {
a := 20 // Actual variable declaration
var ip *int // Pointer variable declaration (initially nil)
ip = &a // Store address of 'a' in pointer variable 'ip'
fmt.Printf("Address of 'a' variable: %p\n", &a)
// Address stored in pointer variable 'ip'
fmt.Printf("Address stored in 'ip' variable: %p\n", ip)
// Access the value using the pointer (dereferencing)
fmt.Printf("Value of *ip variable: %d\n", *ip)
// You can also modify the original variable through the pointer
*ip = 30
fmt.Printf("New value of 'a' after modification via pointer: %d\n", a)
}

Output (addresses will vary):

Address of 'a' variable: 0xc000018038
Address stored in 'ip' variable: 0xc000018038
Value of *ip variable: 20
New value of 'a' after modification via pointer: 30

The zero value for any pointer type in Go is nil. A nil pointer does not point to any valid memory location.

Attempting to dereference a nil pointer (i.e., access *ptr when ptr is nil) will cause a runtime panic.

package main
import "fmt"
func main() {
var ptr *int // Declared but not initialized, so ptr is nil
fmt.Printf("The value of ptr is: %p\n", ptr) // Often prints <nil> or 0x0
if ptr == nil {
fmt.Println("ptr is a nil pointer")
}
// The following line would cause a panic:
// fmt.Println(*ptr)
}

Output:

The value of ptr is: <nil>
ptr is a nil pointer

Always check if a pointer is nil before dereferencing it, especially if it might not have been assigned a valid address.

if ptr != nil {
fmt.Println("Value:", *ptr) // Safe to dereference
}

While Go encourages value semantics where possible, pointers are crucial in specific scenarios:

ScenarioDescription & Benefit
Modifying Function ArgumentsPassing a pointer allows a function to modify the original variable passed by the caller.
Avoiding Large CopiesPassing a pointer to a large struct is cheaper than copying the entire struct.
Methods with Pointer ReceiversMethods that need to modify the receiver struct must use a pointer receiver.
Optional Values / Zero ValuesA pointer can be nil to indicate absence, distinct from a type’s actual zero value (e.g., 0 for int).

Go generally discourages pointer arithmetic (directly adding/subtracting from addresses) which is common in C/C++. Access to underlying memory is usually handled via slices or specific functions in the unsafe package for low-level tasks.

Pointers to pointers (**int) are possible but less common in idiomatic Go than in languages like C.