In Go, slices are a flexible and convenient way to work with sequences of elements. They build on arrays to provide dynamic sizing, which is extremely useful in various programming scenarios. One of the common operations when working with slices is the ability to append or prepend elements.

Basic Appending in Go

Appending elements to a slice is straightforward using the built-in append function. Here's a simple example:

package main

import "fmt"

func main() {
    slice := []int{1, 2, 3}
    fmt.Println("Original slice:", slice)

    // Append a single element
    slice = append(slice, 4)
    fmt.Println("After appending 4:", slice)

    // Append multiple elements
    slice = append(slice, 5, 6, 7)
    fmt.Println("After appending 5, 6, 7:", slice)
}

Prepending Elements - A Basic Trick

Go does not provide a direct function for prepending elements to a slice, but we can achieve this by creating a new slice that includes the elements to prepend, followed by the original slice:

package main

import "fmt"

func main() {
    slice := []int{4, 5, 6}
    fmt.Println("Original slice:", slice)

    // Prepend a single element
    elementToPrepend := 3
    slice = append([]int{elementToPrepend}, slice...)
    fmt.Println("After prepending 3:", slice)

    // Prepend multiple elements
    multipleElements := []int{1, 2}
    slice = append(multipleElements, slice...)
    fmt.Println("After prepending 1, 2:", slice)
}

Intermediate: Efficient Prepend with a Buffer

Although the slicing technique is clean, continuous prepending using slices can lead to inefficient reallocations. When efficiency is critical, consider using a strategy that involves allocating a larger buffer than needed upfront:

package main

import "fmt"

func main() {
    // Set an initial capacity to reduce reallocation
    buffer := make([]int, 0, 10)
    slice := buffer
    fmt.Println("Initial buffer:", buffer)

    // Prepend elements with leading capacity
    slice = append([]int{3, 4, 5}, slice...)
    fmt.Println("After prepending with extra capacity:", slice)
}

Advanced Appending: Custom Append Functions

We can encapsulate the logic of appending or prepending into custom functions. This allows for more controlled behavior, such as handling edge cases, extending slices, or conditionally adding elements:

package main

import "fmt"

// Custom function to append an element
func appendElement(slice []int, element int) []int {
    return append(slice, element)
}

// Custom function to prepend an element
func prependElement(slice []int, element int) []int {
    return append([]int{element}, slice...)
}

func main() {
    slice := []int{10, 20, 30}
    fmt.Println("Original slice:", slice)

    slice = appendElement(slice, 40)
    fmt.Println("After custom append of 40:", slice)

    slice = prependElement(slice, 5)
    fmt.Println("After custom prepend of 5:", slice)
}

These methods and techniques allow you to effectively manage slices in Go, handling both addition at the front and the back with ease and scalability.