Skip to content
Theory · Pointers · lesson 3 of 8

A pointer parameter

In the previous lesson a pointer was a slip of paper with a house's address. Now let's hand that slip to a function—this way the function can change your variable rather than its own copy.

First, recall how a function receives its arguments. It gets a copy of each one—like a photocopy of a document. Whatever the function writes on the photocopy, your original stays the same:

GO
package main

import "fmt"

func inc(n int) {
    n++
}

func main() {
    x := 1
    inc(x)
    fmt.Println(x) // 1
}

Let's go step by step:

  1. inc(x) passes a copy of the value of x to the function.
  2. n++ increments that copy n.
  3. fmt.Println(x) prints 1: x in main did not change.

For a function to change the caller's variable, it needs not a copy of the value but the variable's address.

Two kinds of parameters

A function parameter can hold the value itself or the address of a variable. That decides whether the caller sees the changes.

A value parameter

The parameter n int gets a copy of the number, as in the first example. The call inc(x) never changes x: the function works on its own photocopy.

You choose such a parameter when the function only computes a new value. Then returning the result with return is simpler.

A pointer parameter

If the parameter has the type *int, the function receives a pointer and writes through it. At the call, & goes before the variable—that is how you hand the function the slip with the address:

GO
func inc(n *int) {
    *n++
}

func main() {
    x := 1
    inc(&x)
    inc(&x)
    fmt.Println(x) // 3
}

Step by step:

  1. inc(&x) passes the address of x.
  2. Inside the function *n is x itself, and *n++ adds one to it.
  3. The second call inc(&x) adds one more, and fmt.Println(x) prints 3.

The function can read through the same pointer too: fmt.Println(*n) inside inc would print the current value of x.

The ampersand at the call also tells the reader "this function may change the variable". The call inc(&x) might change it. So you choose a pointer parameter when the function must change the caller's variable.

Let's put both kinds of parameters into one program. Press "Next step" and watch x in the main frame: it changes only after the call incPtr(&x).

LiveA value parameter and a pointer parameterstep 1 of 9 · frames: 1

main creates the variable x with the value 1.

Watch x in the main frame

By value or by reference

A pointer parameter does not break the copying rule: the function still gets a copy—of the slip, not of the house. The copy holds the same address, so it leads to the same x.

Interviewers like to ask this: "Does Go pass arguments by value or by reference?" The answer:

  • Go always passes arguments by value;
  • a plain parameter copies the number;
  • a pointer parameter copies the address.

With pass by reference the function would get the caller's variable itself rather than a copy; Go has no such passing.

Slices and map

Slices are a little trickier. A slice is a small note with three fields:

  • a pointer to an array;
  • a length;
  • a capacity.

The function gets a copy of the note, but the pointer in the copy leads to the same array. So writing to an element inside the function is visible outside. A map works the same way: the copy refers to the same table of keys and values.

GO
package main

import "fmt"

func setFirst(s []int, m map[string]int) {
    s[0] = 9
    m["a"] = 1
}

func main() {
    s := []int{0, 0}
    m := map[string]int{}
    setFirst(s, m)
    fmt.Println(s, m) // [9 0] map[a:1]
}

Let's go through the example step by step:

  1. setFirst(s, m) gets copies of the slice's note and of the map.
  2. s[0] = 9 writes into the shared array, and m["a"] = 1 into the shared table.
  3. fmt.Println(s, m) in main prints [9 0] map[a:1]: both writes are visible outside.

You don't need a pointer to a slice or to a map for this. But a slice's length lives in the note itself, and the note is a copy. If the function calls append, the caller does not see the new length. That is why a function that adds elements returns the new slice, the way append does.

Common mistake

A function with a *int parameter was given the variable instead of its address:

GO
x := 1
inc(x) // ✗
fmt.Println(x)
TEXT
cannot use x (variable of type int) as *int value in argument to inc

The program does not compile: the function expects a slip with an address and gets a number. The fix is inc(&x). The reverse mistake does not compile either: a parameter of type int cannot take &x, because &x has the type *int, not int.

Key points

  • Go always passes arguments by value: the function gets a copy.
  • To let a function change your variable, pass its address: a *int parameter, the call inc(&x).
  • A slice or map argument shares its data with the original, but the caller does not see a new length after append.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way