Nil pointers

The zero value of any pointer type is nil. An uninitialised pointer points at nothing. Dereferencing it is a runtime panic, not a silent success. This lesson covers what nil pointers look like, when they are genuinely useful, and how to avoid the crash.

Pointers start out as nil

Any *T variable you declare without initialising is nil:

var p *int

fmt.Println(p)          // <nil>
fmt.Println(p == nil)   // true

You can compare a pointer to nil with == and !=. You can pass a nil pointer to functions, store one in maps and slices, and put one inside a struct field. None of those operations are a problem. The problem only shows up when you try to follow the pointer to a value that does not exist.

Dereferencing nil is a panic

Put this in the playground and watch the program crash:

var p *int
fmt.Println(*p)

Output:

panic: runtime error: invalid memory address or nil pointer dereference

The panic is loud and ends the program (unless you catch it with recover, covered in the Errors chapter). There is no nil-safe dereference operator like ?. in some other languages. *p fails on a nil p, full stop.

Guard with if p != nil

The usual shape for a function that takes a pointer-to-something-maybe-missing is to check first and dereference second:

func greet(name *string) {
    if name == nil {
        fmt.Println("Hello, stranger")
        return
    }
    fmt.Println("Hello,", *name)
}

func main() {
    greet(nil)

    n := "Kamran"
    greet(&n)
}

Output:

Hello, stranger
Hello, Kamran

greet checks for nil before dereferencing. Every Go function that accepts an optional pointer parameter is shaped the same way.

When nil pointers are genuinely useful

Nil is how Go represents "this value is not set". Because int has a zero value of 0 and string has a zero value of "", you cannot tell "absent" from "present with zero" using the plain types alone. A *int can make the distinction: nil means absent; a non-nil pointer means present with some specific value, even if that value happens to be zero.

This shows up in JSON parsing (a missing optional number becomes a nil *int, covered in the Standard library chapter), in configuration that has optional fields, and anywhere else the difference between "zero" and "not provided" matters. The comma-ok form you saw with maps is another way to represent the same idea; pointers give you a third, for single values outside a map.

Always guard before dereferencing

The most common first-time Go crash is dereferencing a pointer you forgot to check. Before every *p in a function body, ask yourself: could p be nil here? If the answer is "yes" or "maybe", add an if p == nil guard. The compiler will not save you.

Nil-safety is idiomatic

If your function accepts a *T parameter, the caller can pass nil and you have to plan for it. Either guard explicitly at the top of the function, or document that the parameter is required and let the caller crash on violation. Whichever you pick, be consistent across your codebase: a reader should not have to guess which functions tolerate nil and which do not.

Task

The starter has an inc function that takes a *int and increments the value it points at. It does not currently guard against nil, so calling inc(nil) crashes.

Fix inc so that:

  • If the pointer is nil, it does nothing (simply returns without dereferencing).
  • Otherwise, it increments *p by one.

The starter's main calls inc(nil) first, then declares x := 41 and calls inc(&x), then prints x. With the fix in place, the first call is a no-op (no panic) and the second leaves x at 42.

Expected output:

42
Expected output
42