Go pass by value or reference: pointers, slices, and maps in Go

Is go pass by value or reference: Go passes arguments by value; golang pass by reference means passing pointers. go pass by value for structs; slices and maps alias shared data. golang pass by reference or value, golang call by reference, pass by reference golang, golang references, and receiver pointers explained.

Published

Updated

Read time 3 min read

Reviewed byDeepak Prasad

Go pass by value or reference: pointers, slices, and maps in Go

Searches such as golang pass by reference, go pass by reference, or golang call by reference usually mean “can the callee change my variable?” In Go the precise answer to is go pass by value or reference is: every function argument is passed by value. What changes is what that value is—an integer, a struct bit pattern, or a pointer address—so golang pass by reference in everyday speech maps to passing a *T (see pointers in Go). Slices and maps feel like pass by reference golang because the copied header or map handle still aliases the same underlying data.

Tested with Go 1.24 on Linux.


Primitives and structs: go pass by value

Calling f(x) copies x. For int, string, or a struct, the callee cannot reassign the caller’s variable; only the copy changes.

go
package main

import "fmt"

func swapCopy(a, b int) {
	a, b = b, a
}

func swapPtr(a, b *int) {
	*a, *b = *b, *a
}

func main() {
	x, y := 1, 2
	swapCopy(x, y)
	fmt.Println("after copy swap:", x, y)
	swapPtr(&x, &y)
	fmt.Println("after pointer swap:", x, y)
}
Output

You should see after copy swap: 1 2 then after pointer swap: 2 1. The pointer parameters are still passed by value—the values are the addresses of x and y.


Slices and maps: golang pass by reference or value in practice

Slices

A slice value is a small header (pointer, length, capacity). It is passed by value, but the pointer inside points at a shared backing array, so changing s[i] in a function is visible to the caller. Reassigning the local slice after append does not update the caller’s header unless you return the new slice or pass a pointer to the slice.

Maps

Maps are reference types: the map value you pass behaves like a handle to shared data, so adding keys inside a function is visible to the caller. To work on an independent copy, clone the map (copy a map in Go).

go
package main

import "fmt"

func bumpFirst(s []int) { s[0] = 99 }

func addKey(m map[string]int) { m["k"] = 1 }

func main() {
	s := []int{1, 2, 3}
	bumpFirst(s)
	fmt.Println(s)

	m := map[string]int{}
	addKey(m)
	fmt.Println(m["k"])
}

Locally you should see the first element become 99 and the map contain k.


Methods: value receiver versus pointer receiver

A method func (e T) M() receives a copy of T (value semantics for the receiver). A method func (e *T) M() receives the pointer value; M can mutate the pointed-to struct. Choosing between them is ordinary API design for methods, not a second calling convention for function arguments.


Summary

Go pass by value or reference is settled: arguments are always passed by value. golang pass by reference and pass by reference golang usually mean passing a pointer so the callee can mutate through it. go pass by value applies to structs and primitives as full copies. Slices and maps look like golang pass by reference or value puzzles because headers and map handles alias shared memory; only operations that replace the header (such as some append results) need an explicit return or *[]T if the caller must see the new slice.


References


Frequently Asked Questions

1. Is Go pass by value or pass by reference?

Go always passes arguments by value. If you pass a pointer, the pointer value (the address) is copied; mutating *p changes the caller variable. There is no separate C++-style reference parameter syntax.

2. Why can a function modify my slice or map without returning them?

A slice value contains a pointer to the backing array, length, and capacity; copying the slice header still points at the same array, so element updates are visible. Maps are reference types; the map handle acts like a pointer to shared map data.

3. Why does append inside a helper not change my caller slice?

append may allocate a new backing array; the function updates its local slice header, not the caller header unless you return the new slice or pass *[]T.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …