Skip to main content

Command Palette

Search for a command to run...

Day 7 - Pointers

Published
5 min read

Before going to variables first let us understand variables and how they store values.

What Are Variables, and How Do They Store Values?

A variable is a named storage location in memory that holds a value.
Each variable is stored at a specific memory address, usually represented in hexadecimal format (e.g., 0xFFAAF123).

Since memory addresses are hard to remember, we assign human-friendly names (variables) to them.
When we access a variable, we are indirectly accessing the value stored at its memory address.


What Is a Pointer?

A pointer is a special variable used to store the memory address of another variable rather than the actual value.

Pointers help us:

  • Access the memory location of a variable
  • Read or modify the actual value stored at that address
  • Improve performance by avoiding unnecessary copies (especially for structs and arrays)

Pointer Operators

OperatorMeaningUsage
&Address operatorReturns memory address of a variable
*Dereference operatorUsed to access the value at the given memory address

Declaring and Initializing Pointers

var s *string // Declaration of a pointer to a string

var a = 34
addr := &a // 'addr' stores the address of variable 'a'

var ptr *int = addr // Initializing pointer 'ptr' with memory address of 'a'

Dereferencing a Pointer

package main
import "fmt"

func main() {
    a := 10
    addrOfA := &a
    fmt.Println("Memory address of 'a':", addrOfA)

    // Dereferencing the pointer
    fmt.Println("Value stored at", addrOfA, "is", *addrOfA)
}

Output:

Memory address of 'a': 0xc00000a0d8
Value stored at 0xc00000a0d8 is 10

Passing by Value vs Passing by Reference

package main
import "fmt"

// increment increments the value stored at the given memory address
func increment(a *int) {
    *a = *a + 1
}

// incrementNormal increments only a copy of the value, not the original
func incrementNormal(a int) {
    a = a + 1
}

type Person struct {
    name    string
    age     int
    address string
}

func changeAge(p *Person) {
    p.age = 23
}

func main() {
    a := 10
    ptrA := &a
    fmt.Println("Memory address of 'a':", ptrA)
    fmt.Println("Value stored at", ptrA, "is", *ptrA)

    // Passing by reference (pointer)
    counter := 0
    increment(&counter)
    fmt.Println("After increment() - counter:", counter)

    // Passing by value (copy)
    counter2 := 0
    incrementNormal(counter2)
    fmt.Println("After incrementNormal() - counter2:", counter2)

    // Struct example
    p1 := Person{name: "John", age: 20, address: "California, USA"}
    changeAge(&p1)
    fmt.Println("Updated Person:", p1)
}

Output:

Memory address of 'a': 0xc00000a0d8
Value stored at 0xc00000a0d8 is 10
After increment() - counter: 1
After incrementNormal() - counter2: 0
Updated Person: {John 23 California, USA}

Reference-Like Types (No need to send pointers)

TypeBehavior
Slices ([]T)Contains a pointer to an underlying array; passing shares data
Maps (map[K]V)Handled internally by reference
Channels (chan T)Reference type enabling shared communication
FunctionsStored and passed by reference
InterfacesHolds a reference to the underlying value

Value Types (Copied on assignment/passing)

TypePointer Recommended?Reason
StructsUsuallyTo avoid copying large data & allow mutation
Arrays ([n]T)YesFull array is copied by default
Basic types (int, string, bool, float64)❌ Usually noLightweight and copied quickly

Example: Slice Passed Without Pointer (Still Affected)

package main
import "fmt"

func modifySlice(slice []int) {
    slice[0] = -1
}

func main() {
    slice := []int{1, 2, 3, 4, 5}
    modifySlice(slice)
    fmt.Println(slice)
}

Output:```

[-1 2 3 4 5]


### Example: Array Passed Without Pointer (Not Affected Unless Pointer is Used)

```go
package main
import "fmt"

func modifyArray(arr [4]int) {
    arr[0] = -1
}

func modifyArrayUsingPtr(arr *[4]int) {
    arr[0] = -1
}

func main() {
    arr := [4]int{2, 3, 4, 5}
    modifyArray(arr)
    fmt.Println(arr)

    modifyArrayUsingPtr(&arr)
    fmt.Println(arr)
}

Output:

[2 3 4 5]
[-1 3 4 5]

When to Use Pointers in Go (Golang)

Pointers in Go allow you to pass references to variables rather than copying their values. This is especially useful in several real-world scenarios.

1. Large Structs (Performance Optimization)

When a struct is large, passing it by value leads to copying all its fields every time it’s passed to a function — which is inefficient. Using pointers avoids copying by passing a reference instead.

package main

import "fmt"

type Employee struct {
    Name     string
    Age      int
    Address  string
    Position string
    Salary   float64
}

// Passing by pointer avoids copying the entire struct
func UpdatePosition(e *Employee, newPosition string) {
    e.Position = newPosition
}

func main() {
    emp := Employee{"John", 30, "NY", "Developer", 60000}
    UpdatePosition(&emp, "Senior Developer")
    fmt.Println(emp.Position) // Output: Senior Developer
}

Since we're using a pointer (*Employee), we modify the original struct without copying it.

2. Mutability (Modify Original Data)

If you need to modify a variable or struct inside a function, you must pass a pointer. Passing by value creates a copy, and changes won't affect the original.

3. Representing Null / Optional Values

Go does not support null for structs or primitive values directly, but pointers can hold a nil value to indicate absence. This is useful when something is optional or may not exist. You can see this struct

    type Person struct {
    Name   string
    Age    int
    Office *string // Optional field using pointer
    }

   func main() {
    office := "Bangalore"
    person1 := Person{"Alice", 25, &office} // Has an office
    person2 := Person{"Bob", 28, nil}      // Office unknown

    fmt.Println(person1.Office) // Output: address reference
    fmt.Println(person2.Office) // Output: <nil>
}

Drawbacks of Using Pointers

DrawbackDescription
Performance OverheadPassing pointers can trigger heap allocation via escape analysis, which is slower than stack allocation.
GC PressureHeap allocations lead to increased garbage collection (GC) activity, causing latency in performance-critical systems.
Nil Pointer Dereference RiskDereferencing a nil pointer causes runtime panics unless explicitly checked, requiring defensive programming.
Increased Code ComplexityPointers add mental overhead, making code harder to read, understand, and maintain—especially for beginners.
Risk of Unintended Side EffectsSince pointers allow shared mutation of data, multiple functions can unknowingly modify the same value, causing hard-to-track bugs.

Key Takeaways

  • Use pointers when you want to modify the original variable or avoid copying large data.
  • Slices, maps, functions, channels, and interfaces behave like references — changes reflect in the original.
  • Arrays and structs are copied unless passed by pointer.

More from this blog

G

GoSprint90

16 posts

Welcome to GoQuest90 — a 90-day journey from absolute beginner to confident Go developer.