In Go, arrays are passed by values. When a function is called, a copy of the array is made and is assigned to the corresponding parameter variable in the function. Hence the called function receives a copy, and not the original.

In this example, all the values in grades array in main() are copied by value into grades array in change(). If I change one of the grade in change(), it won't be reflected in main()
func main() {
    grades := [3]int {1, 2, 3}
    change(&grades)
}

func change(grades [3]int) {
    grades[2] = 20 // caller doesn't see this change
}
Passing large arrays in this way can be inefficient, and any changes that the function makes to array elements affect only the copy, not the original. This behavior is different from languages that implicitly pass arrays by reference, such as C#.

In C#, the reference is passed by value. As arrays in .NET are stored on the heap, we have a reference. That reference is passed by value to the function, meaning that changes to the contents of the array will be seen by the caller. However, if we reassign that reference to another array, that change won't be visible. 
void Modify(int[] data) {
    data[0] = 1; // caller sees this
}
void Assign(int[] data) {
    data = new int[20]; // but not this
}
To pass an array by reference in Go, we have to pass a pointer to that array, so that any modifications the function makes to array elements will be visible to the caller. For example, the following function changes the contents of an array of 3 integers.
func change(ptr *[3]int) {
    ptr[2] = 20
}
You can call this by passing a pointer to the original array:
change(&arr)
Here is a complete example
package main

import (
    "fmt"
)

func main() {
    grades := [3]int {1, 2, 3}

    fmt.Printf("\nOriginal array:\n")
    for i, v := range grades {
        fmt.Printf("arr[%d] = %d\n", i, v)    // prints 1 2 3
    }

    change(&grades)
    fmt.Printf("\nModified grades:\n")
    for i, v := range grades {
        fmt.Printf("arr[%d] = %d\n", i, v)    // prints 1 2 20
    }
}

func change(ptr*[3]int) {
    ptr[2] = 20
}
Although passing a pointer to an array is efficient and allows the called function to change the caller’s variable, arrays are still fixed in size. Hence, slices are recommended, which are passed by reference and are dynamic in size. For example, 
func SliceDemo() {
    grades := []int{1, 2, 3}
    fmt.Println(grades)  // prints [1 2 3]
    changeSlice(grades)
    fmt.Println(grades)  // prints [1 2 20]
}

func changeSlice(grades []int) {
    grades[2] = 20
}
Here is the StackOverflow question that inspired this post, and my answer to it.