Pointer Types and Declaration
How to declare pointer variables in Go, understanding the *T syntax, zero value nil, and type safety rules
A pointer type in Go is written as *T, where T can be any type — a built-in type, a struct, an array, even another pointer type. A variable of type *T stores the memory address of a value of type T. The zero value of any pointer type is the predeclared identifier nil, which means the pointer currently holds no address at all.
Beginners often find pointers intimidating because they involve memory addresses and indirection. A useful mental model: think of a pointer as a slip of paper with a house address written on it. The house is the value; the slip tells you where to find it. A nil pointer is an empty slip — no address, and trying to visit the house it points to will crash your program.
Declaring Pointer Variables
You can declare a pointer variable the same way you declare any other variable, by writing the type after the name.
var p *int
At this point p is nil. It does not point to any valid int. To make it useful, you must store an address in it. The address-of operator & gives you the address of a variable.
var x int = 10
var p *int = &x
Now p holds the address of x. The type of &x is *int, which matches the declared type of p.
Go also allows type inference when you initialize a pointer with & inside a short variable declaration.
x := 10
p := &x // p is of type *int, inferred by the compiler
Type Inference Works Reliably:
When you use := together with &, the compiler sees that you are assigning the address of a value of type int. It therefore gives p the type *int without you having to write it out. This is not a special case — it’s the same type inference that works for all variable declarations in Go.
The new Function
A second way to obtain a non‑nil pointer is the built-in new function. new(T) allocates memory for a value of type T, initializes it to its zero value, and returns a pointer to it — a value of type *T.
p := new(int) // p is *int, points to an int with value 0
new is useful when you need a pointer to a zero value but do not want to create a named variable just to take its address.
Zero Value and nil
An uninitialized pointer is always nil. You cannot read or write through a nil pointer — any attempt to dereference it causes a runtime panic.
var p *int
fmt.Println(*p) // panic: runtime error: invalid memory address or nil pointer dereference
Runtime Panic on nil Dereference:
Dereferencing a nil pointer is one of the most common crashes in Go programs. The compiler does not warn you about it; the panic only happens when that line executes. Always check if p != nil before dereferencing if there is any path that could leave the pointer uninitialized.
Comparing a pointer to nil is straightforward. p == nil is true when p holds no address. This is the only value you can compare a pointer to without involving another pointer variable.
Uninitialised Pointers Are nil by Default:
A pointer variable declared with var inside a function or at package level starts as nil. If you only conditionally assign an address to it later, remember that the nil path must be handled. Struct fields that are pointer types also default to nil.
Strict Typing of Pointers
A pointer type in Go is tied to its base type. A *int variable can only store the address of an int value. You cannot assign the address of a float64 to it, nor can you assign a *int to a *float64 without an explicit conversion, which is only allowed in very specific circumstances (and generally not a good idea).
var i int = 5
var f float64 = 3.14
p := &i // p is *int
// p = &f // compile error: cannot use &f (type *float64) as type *int in assignment
This strictness prevents a whole category of bugs where you might accidentally interpret memory as the wrong type. It also makes type inference more predictable: the compiler never has to guess which base type a pointer points to.
Declaring Multiple Pointers
You can declare and initialise several pointer variables in one var block or with a single short variable declaration.
var (
a int = 20
b int = 30
pa *int = &a
pb *int = &b
)
Or more concisely:
a, b := 20, 30
pa, pb := &a, &b
Each pointer must match the base type of the variable whose address you take. This is always checked at compile time, so type mismatches are caught immediately.
A Complete Example: Declaring, Reading, and Writing Through Pointers
The following program brings together declaration, address-taking, and dereferencing to show how a pointer gives indirect access to a variable.
package main
import "fmt"
func main() {
value := 42
var ptr *int = &value // declare pointer and initialize with address of value
fmt.Println("value =", value)
fmt.Println("ptr =", ptr) // prints memory address
fmt.Println("*ptr =", *ptr) // dereference: read value through pointer
*ptr = 99 // write through pointer
fmt.Println("value after *ptr = 99:", value)
}
When you run this, you’ll see output similar to:
value = 42
ptr = 0xc0000120a8
*ptr = 42
value after *ptr = 99: 99
The address 0xc0000120a8 will differ on your machine. The critical observation is that modifying *ptr changed value directly — there is only one integer in memory, and ptr holds its address. Beginners sometimes expect that *ptr is a separate copy; it is not. The * operator in this context means “follow the pointer and give me the actual variable that lives there.”
Dereferencing Reads and Writes the Original:
When you use *p on the left side of an assignment, you are writing to the memory location p points to. On the right side, you are reading from it. Both operations go to the same underlying variable. This is why pointers let multiple parts of a program share and mutate the same data without making copies.
Common Beginner Mistakes
Assuming a Pointer Holds a Value After Declaration
Declaring var p *int gives you a nil pointer, not a pointer to an int with value 0. Beginners sometimes write:
var p *int
*p = 5 // panic
The fix is either to assign a valid address first:
var x int
var p *int = &x
*p = 5 // ok
or use new:
p := new(int)
*p = 5 // ok
Forgetting to Check for nil Before Use
When a function may return a pointer that could be nil, you must guard against it. This is especially common when working with structs, maps, or slices that contain pointer fields.
func maybeGetPtr() *int {
// some logic that sometimes returns nil
return nil
}
func main() {
p := maybeGetPtr()
// *p = 10 // unsafe if p is nil
if p != nil {
*p = 10
}
}
Confusing the Pointer and the Value
It takes time to internalize that p and *p are two different things. p is the address; *p is what lives at that address. Printing p shows a hex number; printing *p shows the integer. Assigning p = &anotherVar makes the pointer point to a different variable; it does not change the original variable.
Practical Context
In everyday Go code, pointer declarations appear most often in two situations:
- Struct fields that may be absent or mutable: a
*stringfield in a configuration struct can benilto mean “not set,” while an emptystringwould be ambiguous. - Function parameters and receivers: you declare a parameter as
*Twhen you need the function to modify the original variable or when you want to avoid copying a large struct.
In both cases, the declaration pattern is the same *T syntax covered here. The mental model of a pointer as a signpost to the real data stays constant, no matter how complex the surrounding code becomes.
Summary
A pointer type *T describes a variable that stores the address of a T value. Declaring a pointer does not automatically give it a valid address — it starts as nil. You provide a valid address using the & operator, or you allocate one with new. Go enforces strict type matching: a *int pointer only accepts addresses of int values, which eliminates whole classes of memory errors. The most common mistake with pointer declaration is forgetting that a fresh pointer is nil and trying to dereference it without an explicit check. When you internalize the distinction between the pointer and the value it points to, you have the foundation for understanding pointer parameters, receivers, and dynamic allocation in the chapters ahead.