Arrays vs Slices Golang: Perbedaan, Performance & Best Practices 2025

17 min read
Arrays vs Slices Golang: Perbedaan, Performance & Best Practices 2025

Pada tutorial kali ini di JadiFullstack, saya ingin berbagi pengalaman pribadi saat pertama kali belajar Golang. Dulu, saya juga sempat bingung membedakan arrays dan slices—apalagi saat harus memilih mana yang paling efisien untuk aplikasi yang sedang saya buat. Kalau kamu juga pernah merasakan hal yang sama, artikel ini cocok banget untukmu. kita akan mempelajari perbedaan arrays dan slices di Golang secara mendalam. Kedua data struktur ini merupakan fundamental dalam Golang, namun memiliki karakteristik dan penggunaan yang berbeda.

Artikel ini akan membahas perbedaan arrays dan slices, memory management, performance comparison, dan best practices untuk penggunaan optimal dalam project Go Anda.

Table of Contents

  1. Apa itu Arrays dan Slices di Golang?
  2. Arrays di Golang
  3. Slices di Golang
  4. Advanced Slice Operations
  5. Performance Comparison
  6. Use Cases dan Best Practices
  7. Common Pitfalls dan Solutions
  8. FAQ: Arrays vs Slices Golang

Apa itu Arrays dan Slices di Golang?

Arrays dan Slices adalah dua cara utama untuk menyimpan kumpulan data di Golang:

  • Arrays: Kumpulan data dengan jumlah elemen tetap (fixed size) yang sudah ditentukan sejak awal program dijalankan.
  • Slices: Versi yang lebih fleksibel dari array yang ukurannya bisa berubah-ubah, meskipun tetap menggunakan array di belakang layar.

Mengapa Perbedaan Arrays dan Slices Penting untuk Go Developer?

Memahami arrays vs slices Golang penting untuk:

  • Memory management yang efisien dalam aplikasi Go
  • Performance optimization untuk aplikasi dengan performa tinggi
  • Pemilihan struktur data yang tepat sesuai kebutuhan use case
  • Berinteraksi dengan built-in functions dan pustaka standar Go

Arrays di Golang: Struktur Data Berukuran Tetap

Array di Go memiliki ukuran tetap yang sudah ditentukan saat program dikompilasi, dan bersifat sebagai tipe nilai (value type).

Array Declaration dan Initialization

package main

import "fmt"

func main() {
    // Cara 1: Membuat array kosong dengan nilai default (zero values)
    var nilaiUjian [5]int // [0 0 0 0 0] - 5 elemen dengan nilai 0
    fmt.Printf("Array kosong: %v\n", nilaiUjian)
    
    // Cara 2: Membuat array langsung dengan isi
    buahFavorit := [3]string{"mangga", "jeruk", "apel"}
    fmt.Printf("Buah favorit: %v\n", buahFavorit)
    
    // Cara 3: Biarkan Go menghitung ukuran array otomatis
    warnaPerangi := [...]string{"merah", "putih", "biru", "kuning"}
    fmt.Printf("Warna bendera: %v (jumlah: %d)\n", warnaPerangi, len(warnaPerangi))
    
    // Cara 4: Mengisi array di posisi tertentu
    scoreMatematika := [5]int{1: 85, 3: 92, 4: 78} // posisi 0,2 = 0
    fmt.Printf("Nilai matematika: %v\n", scoreMatematika)
    
    // Cara 5: Array 2 dimensi (seperti tabel)
    jadwalPiket := [3][3]string{
        {"Senin", "Selasa", "Rabu"},
        {"Andi", "Budi", "Citra"},
        {"Dina", "Eko", "Fitri"},
    }
    fmt.Printf("Jadwal piket kelas: %v\n", jadwalPiket)
}

Array Operations

package main

import "fmt"

func main() {
    // Contoh: Menyimpan harga makanan di kantin sekolah
    hargaMakanan := [5]int{5000, 3000, 8000, 2500, 6000}
    
    // Mengakses elemen array (dimulai dari index 0)
    fmt.Printf("Harga nasi gudeg: Rp %d\n", hargaMakanan[0])
    fmt.Printf("Harga termahal: Rp %d\n", hargaMakanan[len(hargaMakanan)-1])
    
    // Mengubah harga (misal ada diskon)
    hargaMakanan[2] = 7000 // Turunkan harga dari 8000 ke 7000
    fmt.Printf("Setelah diskon: %v\n", hargaMakanan)
    
    // Mengetahui jumlah menu
    fmt.Printf("Total menu makanan: %d\n", len(hargaMakanan))
    
    // Cara 1: Loop menggunakan for biasa
    fmt.Print("Daftar harga (for loop): ")
    for i := 0; i < len(hargaMakanan); i++ {
        fmt.Printf("Rp%d ", hargaMakanan[i])
    }
    fmt.Println()
    
    // Cara 2: Loop menggunakan range (lebih mudah)
    fmt.Print("Daftar lengkap: ")
    for urutan, harga := range hargaMakanan {
        fmt.Printf("Menu-%d: Rp%d ", urutan+1, harga)
    }
    fmt.Println()
    
    // Cara 3: Hanya ambil harganya saja (abaikan index)
    fmt.Print("Hanya harga: ")
    for _, harga := range hargaMakanan {
        fmt.Printf("Rp%d ", harga)
    }
    fmt.Println()
}

Array as Value Types

package main

import "fmt"

// Function yang menerima array sebagai kopian (by value)
func ubahNilaiSiswa(nilaiRapor [5]int) {
    nilaiRapor[0] = 100 // Mengubah nilai pertama
    fmt.Printf("Nilai di dalam function: %v\n", nilaiRapor)
}

// Function yang menerima array asli (by reference)
func ubahNilaiSiswaAsli(nilaiRapor *[5]int) {
    nilaiRapor[0] = 100 // Mengubah nilai pertama di array asli
    fmt.Printf("Nilai di dalam function (asli): %v\n", *nilaiRapor)
}

func main() {
    // Contoh: Nilai rapor siswa
    nilaiAsli := [5]int{80, 75, 90, 85, 88}
    fmt.Printf("Nilai rapor asli: %v\n", nilaiAsli)
    
    // Passing by value (kirim kopian) - array asli tidak berubah
    ubahNilaiSiswa(nilaiAsli)
    fmt.Printf("Setelah function kopian: %v\n", nilaiAsli) // Tidak berubah!
    
    // Passing by reference (kirim alamat asli) - array asli berubah
    ubahNilaiSiswaAsli(&nilaiAsli)
    fmt.Printf("Setelah function asli: %v\n", nilaiAsli) // Berubah!
    
    // Array assignment juga membuat kopian
    nilaiCadangan := nilaiAsli
    nilaiCadangan[1] = 95
    fmt.Printf("Nilai asli: %v\n", nilaiAsli)
    fmt.Printf("Nilai cadangan: %v\n", nilaiCadangan)
}

Array Comparison

package main

import "fmt"

func main() {
    // Arrays bisa dibandingkan jika memiliki tipe dan ukuran yang sama
    jadwalA := [3]string{"senin", "selasa", "rabu"}
    jadwalB := [3]string{"senin", "selasa", "rabu"}
    jadwalC := [3]string{"senin", "selasa", "kamis"}
    
    fmt.Printf("Jadwal A sama dengan B: %t\n", jadwalA == jadwalB) // true
    fmt.Printf("Jadwal A sama dengan C: %t\n", jadwalA == jadwalC) // false
    
    // Arrays dengan ukuran berbeda TIDAK bisa dibandingkan
    // var jadwalD [4]string  
    // fmt.Println(jadwalA == jadwalD) // Error kompilasi!
    
    // Contoh perbandingan dengan angka
    nilaiKelas1 := [3]int{85, 90, 78}
    nilaiKelas2 := [3]int{85, 90, 78}
    fmt.Printf("Nilai kedua kelas sama: %t\n", nilaiKelas1 == nilaiKelas2) // true
}

Slices di Golang: Array Dinamis yang Fleksibel

Slice adalah tipe referensi (reference type) yang menyediakan array dengan ukuran dinamis dan fleksibel. Slice merupakan struktur data yang paling umum digunakan dalam pemrograman Go.

Slice Declaration dan Initialization

package main

import "fmt"

func main() {
    // Cara 1: Slice kosong (nil slice)
    var daftarBelanja []string
    fmt.Printf("Daftar kosong: %v (panjang: %d, kapasitas: %d)\n", 
        daftarBelanja, len(daftarBelanja), cap(daftarBelanja))
    
    // Cara 2: Membuat slice dengan make (ukuran tetap dulu)
    nilaiUlangan := make([]int, 5)        // panjang 5, kapasitas 5
    fmt.Printf("Nilai ulangan: %v (panjang: %d, kapasitas: %d)\n", 
        nilaiUlangan, len(nilaiUlangan), cap(nilaiUlangan))
    
    // Cara 3: Membuat slice dengan kapasitas lebih besar
    skorGame := make([]int, 3, 10)    // panjang 3, kapasitas 10
    fmt.Printf("Skor game: %v (panjang: %d, kapasitas: %d)\n", 
        skorGame, len(skorGame), cap(skorGame))
    
    // Cara 4: Slice literal (langsung isi)
    makananFavorit := []string{"rendang", "gudeg", "bakso"}
    fmt.Printf("Makanan favorit: %v (panjang: %d, kapasitas: %d)\n", 
        makananFavorit, len(makananFavorit), cap(makananFavorit))
    
    // Cara 5: Slice kosong tapi bukan nil
    tempatWisata := []string{}
    fmt.Printf("Tempat wisata: %v (panjang: %d, kapasitas: %d)\n", 
        tempatWisata, len(tempatWisata), cap(tempatWisata))
    
    // Perbedaan nil slice vs empty slice
    fmt.Printf("Daftar belanja nil? %t\n", daftarBelanja == nil)
    fmt.Printf("Tempat wisata nil? %t\n", tempatWisata == nil)
}

Slice Operations

package main

import "fmt"

func main() {
    // Membuat slice dari array (seperti memotong kue)
    daftarAngka := [6]int{10, 20, 30, 40, 50, 60}
    
    potongan1 := daftarAngka[1:4]  // ambil index 1,2,3 = [20 30 40]
    potongan2 := daftarAngka[:3]   // dari awal sampai index 2 = [10 20 30]
    potongan3 := daftarAngka[3:]   // dari index 3 sampai akhir = [40 50 60]
    potongan4 := daftarAngka[:]    // ambil semua = [10 20 30 40 50 60]
    
    fmt.Printf("Array asli: %v\n", daftarAngka)
    fmt.Printf("Potongan 1 [1:4]: %v\n", potongan1)
    fmt.Printf("Potongan 2 [:3]: %v\n", potongan2)
    fmt.Printf("Potongan 3 [3:]: %v\n", potongan3)
    fmt.Printf("Potongan 4 [:]: %v\n", potongan4)
    
    // Operasi append (menambah elemen)
    hobi := []string{"membaca", "menulis", "coding"}
    fmt.Printf("Hobi awal: %v (panjang: %d, kapasitas: %d)\n", 
        hobi, len(hobi), cap(hobi))
    
    // Tambah satu hobi
    hobi = append(hobi, "menggambar")
    fmt.Printf("Setelah tambah 1 hobi: %v (panjang: %d, kapasitas: %d)\n", 
        hobi, len(hobi), cap(hobi))
    
    // Tambah beberapa hobi sekaligus
    hobi = append(hobi, "bernyanyi", "menari", "memasak")
    fmt.Printf("Setelah tambah 3 hobi: %v (panjang: %d, kapasitas: %d)\n", 
        hobi, len(hobi), cap(hobi))
    
    // Menggabungkan dengan slice lain
    hobiLain := []string{"berkebun", "bersepeda", "berenang"}
    hobi = append(hobi, hobiLain...) // Perhatikan tiga titik
    fmt.Printf("Setelah gabung slice: %v (panjang: %d, kapasitas: %d)\n", 
        hobi, len(hobi), cap(hobi))
}

Slice Memory Model

package main

import "fmt"

func main() {
    // Memahami header slice
    original := []int{1, 2, 3, 4, 5}
    fmt.Printf("Original: %v (panjang: %d, kapasitas: %d)\n", 
        original, len(original), cap(original))
    
    // Melakukan slicing membuat header slice baru, tapi tetap menggunakan array yang sama di belakang layar
    sub := original[1:3]
    fmt.Printf("Sub slice: %v (panjang: %d, kapasitas: %d)\n", 
        sub, len(sub), cap(sub))
    
    // Mengubah isi sub slice akan memengaruhi original
    sub[0] = 999
    fmt.Printf("Setelah mengubah sub: %v\n", original)
    fmt.Printf("Sub slice: %v\n", sub)
    
    // Ekspresi slice lengkap untuk mengontrol kapasitas
    aman := original[1:3:3] // [bawah:atas:maks]
    fmt.Printf("Slice aman: %v (panjang: %d, kapasitas: %d)\n", 
        aman, len(aman), cap(aman))
    
    // Menambahkan elemen ke slice aman tidak memengaruhi original
    aman = append(aman, 777)
    fmt.Printf("Setelah append ke slice aman: %v\n", original)
    fmt.Printf("Slice aman: %v\n", aman)
}

Copy Function

package main

import "fmt"

func main() {
    // Menggunakan fungsi copy
    sumber := []int{1, 2, 3, 4, 5}
    
    // Menyalin ke slice dengan panjang yang sama
    tujuan1 := make([]int, len(sumber))
    disalin := copy(tujuan1, sumber)
    fmt.Printf("Sumber: %v\n", sumber)
    fmt.Printf("Tujuan1: %v (disalin %d elemen)\n", tujuan1, disalin)
    
    // Menyalin ke slice yang lebih pendek
    tujuan2 := make([]int, 3)
    disalin = copy(tujuan2, sumber)
    fmt.Printf("Tujuan2: %v (disalin %d elemen)\n", tujuan2, disalin)
    
    // Menyalin ke slice yang lebih panjang
    tujuan3 := make([]int, 7)
    disalin = copy(tujuan3, sumber)
    fmt.Printf("Tujuan3: %v (disalin %d elemen)\n", tujuan3, disalin)
    
    // Mengubah hasil salinan tidak memengaruhi slice sumber
    tujuan1[0] = 999
    fmt.Printf("Setelah mengubah salinan: sumber=%v, tujuan1=%v\n", sumber, tujuan1)
    
    // Menyalin slice yang saling tumpang tindih
    slice := []int{1, 2, 3, 4, 5}
    copy(slice[2:], slice[1:]) // Menggeser elemen
    fmt.Printf("Setelah copy tumpang tindih: %v\n", slice)
}

Advanced Slice Operations

Dynamic Growth dan Capacity Management

package main

import "fmt"

func demonstrateGrowth() {
    slice := make([]int, 0, 1)
    fmt.Printf("Initial: len=%d, cap=%d\n", len(slice), cap(slice))
    
    for i := 0; i < 20; i++ {
        slice = append(slice, i)
        fmt.Printf("After append %d: len=%d, cap=%d\n", 
            i, len(slice), cap(slice))
    }
}

func optimizedGrowth() {
    // Pre-allocate kapasitas yang dibutuhkan
    slice := make([]int, 0, 20)
    fmt.Printf("Pre-allocated: len=%d, cap=%d\n", len(slice), cap(slice))
    
    for i := 0; i < 20; i++ {
        slice = append(slice, i)
    }
    fmt.Printf("Final: len=%d, cap=%d\n", len(slice), cap(slice))
}

func main() {
    fmt.Println("=== Dynamic Growth ===")
    demonstrateGrowth()
    
    fmt.Println("\n=== Optimized Growth ===")
    optimizedGrowth()
}

Slice Tricks dan Patterns

package main

import "fmt"

func main() {
    slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    // Remove element at index
    removeAt := func(s []int, index int) []int {
        return append(s[:index], s[index+1:]...)
    }
    
    // Remove element 4 (index 3)
    slice = removeAt(slice, 3)
    fmt.Printf("After removing index 3: %v\n", slice)
    
    // Insert element at index
    insertAt := func(s []int, index int, value int) []int {
        s = append(s, 0)       // Extend slice
        copy(s[index+1:], s[index:]) // Shift elements
        s[index] = value       // Insert new value
        return s
    }
    
    // Insert 100 at index 2
    slice = insertAt(slice, 2, 100)
    fmt.Printf("After inserting 100 at index 2: %v\n", slice)
    
    // Reverse slice
    reverse := func(s []int) {
        for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
            s[i], s[j] = s[j], s[i]
        }
    }
    
    reverse(slice)
    fmt.Printf("After reverse: %v\n", slice)
    
    // Filter slice
    filter := func(s []int, predicate func(int) bool) []int {
        result := s[:0] // Zero length, same capacity
        for _, v := range s {
            if predicate(v) {
                result = append(result, v)
            }
        }
        return result
    }
    
    // Keep only even numbers
    evens := filter(slice, func(n int) bool { return n%2 == 0 })
    fmt.Printf("Even numbers: %v\n", evens)
}

Multi-dimensional Slices

package main

import "fmt"

func main() {
    // 2D slice
    matrix := make([][]int, 3) // 3 rows
    for i := range matrix {
        matrix[i] = make([]int, 4) // 4 columns each
    }
    
    // Fill matrix
    counter := 1
    for i := range matrix {
        for j := range matrix[i] {
            matrix[i][j] = counter
            counter++
        }
    }
    
    fmt.Println("2D Matrix:")
    for i, row := range matrix {
        fmt.Printf("Row %d: %v\n", i, row)
    }
    
    // Jagged slice (different row lengths)
    jagged := [][]int{
        {1, 2},
        {3, 4, 5, 6},
        {7, 8, 9},
    }
    
    fmt.Println("\nJagged slice:")
    for i, row := range jagged {
        fmt.Printf("Row %d: %v (len: %d)\n", i, row, len(row))
    }
    
    // 3D slice
    cube := make([][][]int, 2)
    for i := range cube {
        cube[i] = make([][]int, 3)
        for j := range cube[i] {
            cube[i][j] = make([]int, 4)
        }
    }
    
    fmt.Printf("\n3D Cube dimensions: %dx%dx%d\n", 
        len(cube), len(cube[0]), len(cube[0][0]))
}

Performance Comparison

Memory Usage Analysis

package main

import (
    "fmt"
    "runtime"
    "unsafe"
)

func getMemUsage() (float64, float64) {
    var m runtime.MemStats
    runtime.GC()
    runtime.ReadMemStats(&m)
    return float64(m.Alloc) / 1024 / 1024, float64(m.Sys) / 1024 / 1024
}

func arrayMemoryUsage() {
    fmt.Println("=== Array Memory Usage ===")
    
    before1, _ := getMemUsage()
    
    // Create large arrays
    arrays := make([1000]*[1000]int, 0)
    for i := 0; i < 1000; i++ {
        arr := &[1000]int{}
        arrays = append(arrays, arr)
    }
    
    after1, _ := getMemUsage()
    fmt.Printf("Arrays: %.2f MB\n", after1-before1)
    
    // Size of array
    var sampleArray [1000]int
    fmt.Printf("Size of [1000]int: %d bytes\n", unsafe.Sizeof(sampleArray))
}

func sliceMemoryUsage() {
    fmt.Println("\n=== Slice Memory Usage ===")
    
    before2, _ := getMemUsage()
    
    // Create large slices
    slices := make([][]int, 0)
    for i := 0; i < 1000; i++ {
        slice := make([]int, 1000)
        slices = append(slices, slice)
    }
    
    after2, _ := getMemUsage()
    fmt.Printf("Slices: %.2f MB\n", after2-before2)
    
    // Size of slice header
    var sampleSlice []int
    fmt.Printf("Size of slice header: %d bytes\n", unsafe.Sizeof(sampleSlice))
}

func main() {
    arrayMemoryUsage()
    sliceMemoryUsage()
}

Performance Benchmarks

package main

import (
    "fmt"
    "time"
)

func benchmarkArrayIteration() time.Duration {
    arr := [10000]int{}
    for i := range arr {
        arr[i] = i
    }
    
    start := time.Now()
    sum := 0
    for i := 0; i < 1000; i++ {
        for j := range arr {
            sum += arr[j]
        }
    }
    duration := time.Since(start)
    
    fmt.Printf("Array sum: %d\n", sum)
    return duration
}

func benchmarkSliceIteration() time.Duration {
    slice := make([]int, 10000)
    for i := range slice {
        slice[i] = i
    }
    
    start := time.Now()
    sum := 0
    for i := 0; i < 1000; i++ {
        for _, v := range slice {
            sum += v
        }
    }
    duration := time.Since(start)
    
    fmt.Printf("Slice sum: %d\n", sum)
    return duration
}

func benchmarkSliceAppend() time.Duration {
    start := time.Now()
    
    slice := make([]int, 0)
    for i := 0; i < 10000; i++ {
        slice = append(slice, i)
    }
    
    return time.Since(start)
}

func benchmarkSlicePrealloc() time.Duration {
    start := time.Now()
    
    slice := make([]int, 0, 10000) // Pre-allocate capacity
    for i := 0; i < 10000; i++ {
        slice = append(slice, i)
    }
    
    return time.Since(start)
}

func main() {
    fmt.Println("=== Performance Benchmarks ===")
    
    arrayTime := benchmarkArrayIteration()
    sliceTime := benchmarkSliceIteration()
    
    fmt.Printf("Array iteration: %v\n", arrayTime)
    fmt.Printf("Slice iteration: %v\n", sliceTime)
    fmt.Printf("Difference: %.2fx\n", float64(sliceTime)/float64(arrayTime))
    
    appendTime := benchmarkSliceAppend()
    preallocTime := benchmarkSlicePrealloc()
    
    fmt.Printf("\nSlice append: %v\n", appendTime)
    fmt.Printf("Slice prealloc: %v\n", preallocTime)
    fmt.Printf("Prealloc is %.2fx faster\n", float64(appendTime)/float64(preallocTime))
}

Use Cases dan Best Practices

When to Use Arrays

package main

import "fmt"

// Arrays cocok untuk:
// 1. Data dengan ukuran tetap yang sudah diketahui
type WarnaRGB [3]uint8

func (w WarnaRGB) String() string {
    return fmt.Sprintf("RGB(%d, %d, %d)", w[0], w[1], w[2])
}

// 2. Operasi matematika dengan dimensi tetap
type Matriks3x3 [3][3]float64

func (m Matriks3x3) Kali(lain Matriks3x3) Matriks3x3 {
    var hasil Matriks3x3
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            for k := 0; k < 3; k++ {
                hasil[i][j] += m[i][k] * lain[k][j]
            }
        }
    }
    return hasil
}

// 3. Tabel referensi dan konstanta
var hariBulan = [12]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}

func dapatkanHariBulan(bulan int) int {
    if bulan < 1 || bulan > 12 {
        return 0
    }
    return hariBulan[bulan-1]
}

func main() {
    // Contoh warna RGB
    merah := WarnaRGB{255, 0, 0}
    fmt.Printf("Warna merah: %v\n", merah)
    
    // Contoh operasi matriks
    identitas := Matriks3x3{
        {1, 0, 0},
        {0, 1, 0},
        {0, 0, 1},
    }
    
    skala := Matriks3x3{
        {2, 0, 0},
        {0, 2, 0},
        {0, 0, 2},
    }
    
    hasil := identitas.Kali(skala)
    fmt.Printf("Hasil perkalian matriks: %v\n", hasil)
    
    // Contoh tabel referensi
    fmt.Printf("Hari di bulan Februari: %d\n", dapatkanHariBulan(2))
    fmt.Printf("Hari di bulan Desember: %d\n", dapatkanHariBulan(12))
}

When to Use Slices

package main

import (
    "fmt"
    "sort"
    "strings"
)

// Slices cocok untuk:
// 1. Koleksi data yang berubah-ubah
type DaftarBelanja struct {
    barang []string
}

func (db *DaftarBelanja) Tambah(item string) {
    db.barang = append(db.barang, item)
}

func (db *DaftarBelanja) Hapus(item string) {
    for i, barang := range db.barang {
        if barang == item {
            db.barang = append(db.barang[:i], db.barang[i+1:]...)
            break
        }
    }
}

func (db *DaftarBelanja) DaftarLengkap() string {
    return strings.Join(db.barang, ", ")
}

// 2. Pemrosesan data bertahap
func ProsesAngka(angka []int) []int {
    // Filter hanya angka positif
    positif := make([]int, 0, len(angka))
    for _, n := range angka {
        if n > 0 {
            positif = append(positif, n)
        }
    }
    
    // Kuadratkan setiap angka
    kuadrat := make([]int, len(positif))
    for i, n := range positif {
        kuadrat[i] = n * n
    }
    
    // Urutkan dari besar ke kecil
    sort.Slice(kuadrat, func(i, j int) bool {
        return squared[i] > squared[j]
    })
    
    return squared
}

// 3. Buffers dan streaming data
type Buffer struct {
    data []byte
}

func (b *Buffer) Write(p []byte) (int, error) {
    b.data = append(b.data, p...)
    return len(p), nil
}

func (b *Buffer) Read(p []byte) (int, error) {
    if len(b.data) == 0 {
        return 0, fmt.Errorf("buffer is empty")
    }
    
    n := copy(p, b.data)
    b.data = b.data[n:]
    return n, nil
}

func (b *Buffer) Len() int {
    return len(b.data)
}

func main() {
    // Shopping list example
    list := &ShoppingList{}
    list.Add("milk")
    list.Add("bread")
    list.Add("eggs")
    fmt.Printf("Shopping list: %s\n", list.List())
    
    list.Remove("bread")
    fmt.Printf("After removing bread: %s\n", list.List())
    
    // Data processing example
    numbers := []int{-5, 3, -2, 8, 1, -7, 4}
    processed := ProcessNumbers(numbers)
    fmt.Printf("Original: %v\n", numbers)
    fmt.Printf("Processed: %v\n", processed)
    
    // Buffer example
    buffer := &Buffer{}
    buffer.Write([]byte("Hello "))
    buffer.Write([]byte("World!"))
    
    output := make([]byte, 6)
    n, _ := buffer.Read(output)
    fmt.Printf("Read %d bytes: %s\n", n, string(output[:n]))
    fmt.Printf("Remaining buffer length: %d\n", buffer.Len())
}

Common Pitfalls dan Solutions

❌ Common Mistakes

package main

import "fmt"

func main() {
    // MISTAKE 1: Slice append in loop without preallocation
    badAppend := func() []int {
        var result []int
        for i := 0; i < 10000; i++ {
            result = append(result, i) // Multiple reallocations
        }
        return result
    }
    
    // SOLUTION: Preallocate capacity
    goodAppend := func() []int {
        result := make([]int, 0, 10000) // Preallocate
        for i := 0; i < 10000; i++ {
            result = append(result, i) // No reallocations
        }
        return result
    }
    
    // MISTAKE 2: Unexpected slice sharing
    original := []int{1, 2, 3, 4, 5}
    sub1 := original[1:3] // Shares underlying array
    sub2 := original[2:4] // Also shares same array
    
    sub1[1] = 999 // This affects sub2 and original!
    fmt.Printf("Original: %v\n", original) // [1 2 999 4 5]
    fmt.Printf("Sub1: %v\n", sub1)         // [2 999]
    fmt.Printf("Sub2: %v\n", sub2)         // [999 4]
    
    // SOLUTION: Use copy for independence
    original2 := []int{1, 2, 3, 4, 5}
    safeSub := make([]int, 2)
    copy(safeSub, original2[1:3])
    safeSub[1] = 777
    fmt.Printf("Original2: %v\n", original2) // [1 2 3 4 5] - unchanged
    fmt.Printf("SafeSub: %v\n", safeSub)     // [2 777]
    
    // MISTAKE 3: Slice leak dalam loop
    // BAD: This can cause memory leak
    processLargeData := func() []string {
        largeData := make([]string, 1000000)
        // ... fill with data
        return largeData[:10] // Keeps entire array in memory!
    }
    
    // GOOD: Copy needed data
    processLargeDataSafe := func() []string {
        largeData := make([]string, 1000000)
        // ... fill with data
        result := make([]string, 10)
        copy(result, largeData[:10])
        return result // Large array can be garbage collected
    }
    
    _ = badAppend
    _ = goodAppend
    _ = processLargeData
    _ = processLargeDataSafe
}

✅ Best Practices

package main

import "fmt"

// 1. Preallocate slices when size is known
func efficientStringBuilder(words []string) string {
    totalLen := 0
    for _, word := range words {
        totalLen += len(word)
    }
    
    result := make([]byte, 0, totalLen)
    for _, word := range words {
        result = append(result, []byte(word)...)
    }
    
    return string(result)
}

// 2. Use full slice expression untuk safety
func safeSlicing(data []int, start, end int) []int {
    // Full slice expression limits capacity
    return data[start:end:end] // [low:high:max]
}

// 3. Nil slice vs empty slice
func demonstrateNilVsEmpty() {
    var nilSlice []int          // nil slice
    emptySlice := []int{}       // empty slice
    madeSlice := make([]int, 0) // empty slice
    
    fmt.Printf("nilSlice == nil: %t\n", nilSlice == nil)     // true
    fmt.Printf("emptySlice == nil: %t\n", emptySlice == nil) // false
    fmt.Printf("madeSlice == nil: %t\n", madeSlice == nil)   // false
    
    // All can be appended to safely
    nilSlice = append(nilSlice, 1)
    emptySlice = append(emptySlice, 2)
    madeSlice = append(madeSlice, 3)
    
    fmt.Printf("After append - nil: %v, empty: %v, made: %v\n", 
        nilSlice, emptySlice, madeSlice)
}

// 4. Proper error handling dengan slices
func safeSliceAccess(slice []int, index int) (int, error) {
    if index < 0 || index >= len(slice) {
        return 0, fmt.Errorf("index %d out of range [0:%d]", index, len(slice))
    }
    return slice[index], nil
}

func main() {
    words := []string{"hello", "world", "golang"}
    result := efficientStringBuilder(words)
    fmt.Printf("Concatenated: %s\n", result)
    
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    safe := safeSlicing(data, 2, 5)
    fmt.Printf("Safe slice: %v (cap: %d)\n", safe, cap(safe))
    
    demonstrateNilVsEmpty()
    
    if val, err := safeSliceAccess(data, 5); err == nil {
        fmt.Printf("Value at index 5: %d\n", val)
    } else {
        fmt.Printf("Error: %v\n", err)
    }
}

Kesimpulan

Arrays dan Slices di Golang memiliki karakteristik yang berbeda:

Arrays:

  • Fixed size yang ditentukan saat compile time
  • Value types - passing by value creates copy
  • Memory efficient untuk fixed-size data
  • Comparable dengan operator ==
  • Use cases: Matrix operations, lookup tables, konstanta

Slices:

  • Dynamic size yang flexible
  • Reference types - sharing underlying array
  • Built-in growth dengan append function
  • More common dalam Go programming
  • Use cases: Collections, buffers, data processing

Best Practices:

  1. Prefer slices untuk most use cases
  2. Preallocate capacity when size is known
  3. Use copy untuk independence
  4. Be aware of slice sharing behavior
  5. Use full slice expression untuk safety
  6. Handle nil slices properly

Semoga penjelasan dan contoh di atas bisa membantumu lebih paham arrays dan slices di Golang. Pada tutorial selanjutnya, kita akan membahas Maps di Golang yang merupakan built-in hash table implementation yang powerful!


Related Articles: