Bird
Raised Fist0
Goprogramming~15 mins

Passing values vs pointers in Go - Trade-offs & Expert Analysis

Choose your learning style10 modes available
Overview - Passing values vs pointers
What is it?
Passing values vs pointers in Go means deciding whether to send a copy of data or a reference to the original data when calling functions. Passing by value sends a copy, so changes inside the function don't affect the original. Passing by pointer sends the address, so the function can modify the original data. This choice affects how your program uses memory and behaves.
Why it matters
Without understanding passing values vs pointers, you might accidentally change data you didn't want to or waste memory copying large data. This can cause bugs or slow programs. Knowing when to use each helps write efficient, clear, and safe code that behaves as expected.
Where it fits
Before this, you should know basic Go variables, functions, and types. After this, you can learn about methods with pointer receivers, interfaces, and memory management in Go.
Mental Model
Core Idea
Passing by value sends a copy of data, while passing by pointer sends the address to the original data, allowing direct modification.
Think of it like...
It's like giving a friend a photocopy of your homework (value) versus giving them your actual homework notebook (pointer). Changes to the photocopy don't affect your original, but changes to the notebook do.
Function Call
  ├─ Pass by Value ──> [Copy of Data] ──> Function works on copy
  └─ Pass by Pointer -> [Address of Data] -> Function works on original

Memory:
  Original Data [1000] <─ Pointer holds address 1000
  Copy Data [2000] <─ Value is separate copy
Build-Up - 7 Steps
1
FoundationUnderstanding Passing by Value
🤔
Concept: Passing by value means sending a copy of the data to a function.
In Go, when you pass a variable to a function without using pointers, the function receives its own copy. Changes inside the function do not affect the original variable. Example: func changeValue(x int) { x = 10 } func main() { a := 5 changeValue(a) fmt.Println(a) // prints 5 }
Result
The original variable remains unchanged after the function call.
Understanding that passing by value protects the original data from accidental changes helps prevent bugs.
2
FoundationBasics of Passing by Pointer
🤔
Concept: Passing by pointer means sending the memory address of the data to a function.
In Go, you can pass a pointer to a variable using the & operator. The function receives the address and can modify the original data. Example: func changeValue(x *int) { *x = 10 } func main() { a := 5 changeValue(&a) fmt.Println(a) // prints 10 }
Result
The original variable is changed because the function modifies the data at the given address.
Knowing that pointers allow functions to modify original data is key to controlling side effects.
3
IntermediateWhen to Use Value vs Pointer
🤔Before reading on: Do you think passing large structs by value or pointer is more efficient? Commit to your answer.
Concept: Choosing between value and pointer depends on data size and whether you want to modify the original.
Small data like integers or booleans are cheap to copy, so passing by value is fine. Large structs or arrays are costly to copy, so passing pointers saves memory and time. Also, if you want the function to change the original data, use pointers. Example: // Large struct type Person struct { Name string Age int } func updateAge(p *Person) { p.Age = 30 } func main() { person := Person{"Alice", 25} updateAge(&person) fmt.Println(person.Age) // prints 30 }
Result
Using pointers for large data avoids expensive copying and allows modification.
Understanding efficiency and intent guides the choice between value and pointer passing.
4
IntermediatePointer Syntax and Dereferencing
🤔Before reading on: Does using a pointer variable automatically access the original data, or do you need to dereference it? Commit to your answer.
Concept: Pointers hold addresses; to access or change the original data, you must dereference them with *.
In Go, * is used to get or set the value at the pointer's address. The & operator gets the address of a variable. Example: var x int = 5 var p *int = &x // p holds address of x fmt.Println(*p) // prints 5 *p = 10 // changes x to 10 fmt.Println(x) // prints 10
Result
Dereferencing pointers lets you read or modify the original variable.
Knowing how to use & and * correctly prevents common pointer mistakes.
5
IntermediatePassing Pointers to Structs and Slices
🤔
Concept: Structs and slices behave differently with pointers; slices are reference types but structs are value types.
Structs are copied when passed by value, so use pointers to modify them. Slices hold pointers internally, so passing a slice by value still allows modifying the underlying array. Example: func modifySlice(s []int) { s[0] = 100 } func modifyStruct(p *Person) { p.Name = "Bob" } func main() { nums := []int{1, 2, 3} modifySlice(nums) fmt.Println(nums[0]) // prints 100 person := Person{"Alice", 25} modifyStruct(&person) fmt.Println(person.Name) // prints Bob }
Result
Slices can be modified without pointers; structs need pointers to modify original data.
Understanding Go's slice internals clarifies when pointers are needed.
6
AdvancedPointer Semantics in Method Receivers
🤔Before reading on: Do methods with pointer receivers modify the original struct or a copy? Commit to your answer.
Concept: Methods can have value or pointer receivers, affecting whether they modify the original struct or a copy.
If a method has a pointer receiver, it can change the struct's fields. If it has a value receiver, it works on a copy. Example: type Counter struct { Count int } func (c *Counter) Increment() { c.Count++ } func (c Counter) Reset() { c.Count = 0 } func main() { c := Counter{5} c.Increment() fmt.Println(c.Count) // prints 6 c.Reset() fmt.Println(c.Count) // still prints 6 }
Result
Pointer receiver methods modify the original; value receiver methods do not.
Knowing method receiver types helps control when structs are changed.
7
ExpertSubtle Pointer Pitfalls and Escape Analysis
🤔Before reading on: Does passing a pointer always improve performance? Commit to your answer.
Concept: Passing pointers can sometimes hurt performance due to Go's escape analysis and memory allocation behavior.
Go's compiler decides if variables escape to the heap or stay on the stack. Passing pointers may cause variables to escape, increasing garbage collection overhead. Also, small structs passed by pointer might be slower due to indirection. Example: func process(p *Person) { // p may cause heap allocation } func main() { person := Person{"Alice", 25} process(&person) } Sometimes passing by value is faster for small structs. Understanding escape analysis helps write efficient code.
Result
Pointer use can increase heap allocations and reduce performance if not used carefully.
Knowing Go's escape analysis prevents blindly using pointers and helps optimize memory use.
Under the Hood
When you pass by value, Go copies the data into a new memory location for the function. When you pass a pointer, Go copies the address (a small fixed-size value) instead. The function uses this address to access or modify the original data in memory. The compiler and runtime manage these copies and references, deciding where variables live (stack or heap) based on usage.
Why designed this way?
Go was designed for simplicity and performance. Passing by value avoids unintended side effects by default, making code safer. Pointers provide controlled access to original data when needed. This balance helps prevent bugs common in languages with unrestricted pointers, while still allowing efficient memory use.
Caller Stack Frame
┌─────────────────────┐
│ Original Variable    │
│ Value: 42           │
│ Address: 0xc0000140  │
└─────────┬───────────┘
          │
          │ Pass by Value (copy)
          ▼
Function Stack Frame
┌─────────────────────┐
│ Parameter Variable   │
│ Value: 42 (copy)    │
└─────────────────────┘

OR

Caller Stack Frame
┌─────────────────────┐
│ Original Variable    │
│ Value: 42           │
│ Address: 0xc0000140  │
└─────────┬───────────┘
          │
          │ Pass by Pointer (address)
          ▼
Function Stack Frame
┌─────────────────────┐
│ Parameter Variable   │
│ Value: 0xc0000140   │
└─────────────────────┘
          │
          ▼
Dereference to access original data
Myth Busters - 4 Common Misconceptions
Quick: Does passing a slice by value prevent modifying its elements? Commit to yes or no.
Common Belief:Passing a slice by value means the function cannot change the original slice's elements.
Tap to reveal reality
Reality:Slices are reference types; passing by value copies the slice header but not the underlying array, so the function can modify the original elements.
Why it matters:Assuming slices are fully copied can lead to unexpected data changes and bugs.
Quick: Does passing a pointer always improve performance? Commit to yes or no.
Common Belief:Using pointers always makes the program faster by avoiding copying data.
Tap to reveal reality
Reality:For small data, passing by value can be faster. Pointers add indirection and may cause heap allocations, hurting performance.
Why it matters:Blindly using pointers can degrade performance and increase garbage collection.
Quick: If a function receives a pointer, does it always modify the original data? Commit to yes or no.
Common Belief:If a function takes a pointer, it will always change the original variable.
Tap to reveal reality
Reality:The function can choose not to modify the data; passing a pointer only allows modification but doesn't force it.
Why it matters:Assuming all pointer parameters change data can cause confusion and unnecessary defensive code.
Quick: Does a method with a value receiver modify the original struct? Commit to yes or no.
Common Belief:Methods with value receivers can change the original struct's fields.
Tap to reveal reality
Reality:Value receiver methods work on copies; they cannot change the original struct's fields.
Why it matters:Misunderstanding this leads to bugs where changes seem ignored.
Expert Zone
1
Pointer receivers are necessary for methods that modify the receiver or to avoid copying large structs, but using them everywhere can reduce code clarity.
2
Escape analysis can cause variables to be allocated on the heap when pointers are passed, impacting performance in subtle ways.
3
Slices and maps are reference types internally, so passing them by value still allows modification of their contents, unlike arrays.
When NOT to use
Avoid pointers when working with small, immutable data where copying is cheap and safer. Use value receivers for methods that do not modify the receiver to keep code simple. For concurrency, avoid sharing pointers without synchronization to prevent race conditions.
Production Patterns
In real-world Go code, pointer receivers are common for structs representing entities or resources to allow efficient updates. Functions often accept pointers for large structs to reduce copying. Immutable data is passed by value to avoid side effects. Understanding escape analysis guides performance tuning in critical code.
Connections
Memory Management
Passing pointers relates directly to how memory is allocated and accessed.
Knowing how passing pointers affects memory allocation helps optimize program speed and resource use.
Functional Programming
Passing by value aligns with immutability principles in functional programming.
Understanding value passing clarifies how to write side-effect-free functions and safer code.
Human Communication
Passing values vs pointers is like sending copies of messages versus giving direct access to your notes.
This connection shows how controlling information sharing affects trust and control, similar to programming data.
Common Pitfalls
#1Modifying a copy when intending to change original data
Wrong approach:func update(x int) { x = 10 } func main() { a := 5 update(a) fmt.Println(a) // prints 5, not 10 }
Correct approach:func update(x *int) { *x = 10 } func main() { a := 5 update(&a) fmt.Println(a) // prints 10 }
Root cause:Passing by value copies data, so changes inside the function don't affect the original.
#2Assuming passing a slice by value prevents modification of elements
Wrong approach:func modify(s []int) { s[0] = 100 } func main() { nums := []int{1, 2, 3} modify(nums) fmt.Println(nums[0]) // prints 100 }
Correct approach:No change needed; this is correct behavior.
Root cause:Slices hold pointers internally, so passing them by value still allows modifying the underlying array.
#3Using pointer receivers unnecessarily for small structs
Wrong approach:func (p *Point) Distance() float64 { // method body } // Point is a small struct with two floats
Correct approach:func (p Point) Distance() float64 { // method body } // Use value receiver for small structs when no modification needed
Root cause:Unnecessary pointers add indirection and reduce code clarity without performance benefit.
Key Takeaways
Passing by value sends a copy of data, protecting the original from changes inside functions.
Passing by pointer sends the address, allowing functions to modify the original data directly.
Use value passing for small, immutable data and pointer passing for large data or when modification is needed.
Slices are reference types; passing them by value still allows modifying their elements.
Understanding Go's escape analysis helps avoid performance pitfalls when using pointers.

Practice

(1/5)
1. What happens when you pass a variable to a function by value in Go?
easy
A. The function receives a copy; changes inside do not affect the original variable.
B. The function receives the original variable; changes inside affect the original.
C. The function cannot modify the variable at all.
D. The function receives a reference but cannot change the original variable.

Solution

  1. Step 1: Understand passing by value

    Passing by value means the function gets a copy of the variable, not the original.
  2. Step 2: Effect of changes inside the function

    Changes to the copy do not affect the original variable outside the function.
  3. Final Answer:

    The function receives a copy; changes inside do not affect the original variable. -> Option A
  4. Quick Check:

    Passing by value = copy, no original change [OK]
Hint: Passing by value means function works on a copy [OK]
Common Mistakes:
  • Thinking changes inside function affect original variable
  • Confusing pointers with values
  • Assuming function cannot modify any data
2. Which of the following is the correct way to declare a function parameter as a pointer in Go?
easy
A. func updateValue(val *int) {}
B. func updateValue(&val int) {}
C. func updateValue(val int) {}
D. func updateValue(*val int) {}

Solution

  1. Step 1: Recall pointer syntax in Go function parameters

    In Go, a pointer parameter is declared by placing * before the type, e.g., *int.
  2. Step 2: Identify correct syntax

    func updateValue(val *int) {} is correct syntax, with * before the type. func updateValue(val int) {} declares a value parameter. func updateValue(&val int) {} and func updateValue(*val int) {} have invalid syntax.
  3. Final Answer:

    func updateValue(val *int) {} -> Option A
  4. Quick Check:

    Pointer parameter syntax = *type [OK]
Hint: Pointer parameter uses * before type, not variable name [OK]
Common Mistakes:
  • Putting * before variable name instead of type
  • Using & in parameter declaration
  • Confusing pointer syntax with value parameter
3. What is the output of this Go program?
package main
import "fmt"
func changeValue(val int) {
    val = 10
}
func main() {
    x := 5
    changeValue(x)
    fmt.Println(x)
}
medium
A. 10
B. 5
C. 0
D. Compilation error

Solution

  1. Step 1: Analyze function parameter passing

    The function changeValue receives x by value, so val is a copy of x.
  2. Step 2: Check if original variable changes

    Inside changeValue, val is set to 10, but this does not affect x in main.
  3. Final Answer:

    5 -> Option B
  4. Quick Check:

    Passing by value means original x stays 5 [OK]
Hint: Passing by value means original variable unchanged [OK]
Common Mistakes:
  • Assuming x changes to 10 after function call
  • Confusing pointer and value passing
  • Expecting compilation error due to assignment
4. Identify the error in this Go code snippet:
func increment(num *int) {
    num = num + 1
}

func main() {
    x := 5
    increment(&x)
    fmt.Println(x)
}
medium
A. Missing & operator when calling increment.
B. x is not initialized before use.
C. Function parameter should not be a pointer.
D. Cannot add 1 to a pointer directly; need to dereference first.

Solution

  1. Step 1: Understand pointer arithmetic in Go

    Go does not support arithmetic on pointers like 'num + 1'. You must dereference the pointer to change the value.
  2. Step 2: Correct way to increment value pointed by pointer

    Use '*num = *num + 1' to increment the value at the pointer address.
  3. Final Answer:

    Cannot add 1 to a pointer directly; need to dereference first. -> Option D
  4. Quick Check:

    Pointer arithmetic invalid; dereference to modify [OK]
Hint: Use * to access pointer value before arithmetic [OK]
Common Mistakes:
  • Trying to add directly to pointer variable
  • Forgetting to dereference pointer
  • Confusing pointer and value types
5. You want to write a function in Go that doubles the value of an integer variable passed to it. Which approach correctly modifies the original variable?
hard
A. Return the doubled value and assign it outside the function.
B. Pass the integer by value and assign double inside the function.
C. Pass a pointer to the integer and modify the value via dereferencing.
D. Use a global variable instead of passing parameters.

Solution

  1. Step 1: Understand how to modify original variable

    Passing by value copies the variable, so changes inside function won't affect original.
  2. Step 2: Use pointer to modify original variable

    Passing a pointer allows the function to access and change the original variable's value via dereferencing.
  3. Step 3: Consider alternatives

    Returning doubled value requires assignment outside; using global variables is bad practice.
  4. Final Answer:

    Pass a pointer to the integer and modify the value via dereferencing. -> Option C
  5. Quick Check:

    Pointer passing modifies original variable [OK]
Hint: Use pointer to change original variable inside function [OK]
Common Mistakes:
  • Assuming passing by value changes original variable
  • Relying on return value without assignment
  • Using global variables unnecessarily
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard