unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP

Perulangan #

Go hanya punya satu keyword untuk perulangan: for. Tidak ada while. Tidak ada do-while. Tidak ada foreach. Keputusan ini bukan karena Go tidak mampu — ini disengaja untuk menjaga konsistensi. Ketika semua developer Go menggunakan satu keyword yang sama untuk semua pola perulangan, kode dari satu developer langsung mudah dibaca oleh developer lain. Dan ternyata, for yang satu itu cukup fleksibel untuk mengekspresikan semua pola perulangan yang pernah ada.

Tiga Bentuk for #

for di Go bisa ditulis dalam tiga bentuk utama, masing-masing punya konteks penggunaan yang tepat.

Bentuk 1: Classic Three-Component #

Bentuk paling familiar bagi developer yang datang dari C, Java, atau JavaScript:

// for init; condition; post { }
for i := 0; i < 5; i++ {
    fmt.Println(i)
}
// Output: 0 1 2 3 4

Tiga komponen dipisahkan titik koma:

  • init (i := 0) — dieksekusi sekali sebelum iterasi pertama; variabel yang dideklarasikan di sini hanya hidup dalam scope loop
  • condition (i < 5) — diperiksa sebelum setiap iterasi; loop berhenti ketika false
  • post (i++) — dieksekusi setelah setiap iterasi sebelum pengecekan condition berikutnya

Setiap komponen bersifat opsional — kamu bisa menghilangkan salah satu atau keduanya:

// Hanya condition — setara while
i := 0
for i < 5 {
    fmt.Println(i)
    i++
}

// Hanya init dan post — condition selalu true → infinite loop
for i := 0; ; i++ {
    if i >= 5 {
        break
    }
    fmt.Println(i)
}

// Tanpa semua komponen → infinite loop
for {
    // loop selamanya
}

Bentuk 2: Condition Only (setara while) #

Ketika hanya ada kondisi, for berperilaku seperti while di bahasa lain:

// Membaca input sampai kondisi terpenuhi
attempts := 0
for attempts < maxRetry {
    err := tryConnect()
    if err == nil {
        break
    }
    attempts++
    time.Sleep(time.Second * time.Duration(attempts))
}

// Proses data stream sampai habis
scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    processLine(line)
}

Bentuk 3: Infinite Loop #

for tanpa apapun adalah infinite loop — bukan code smell, tapi pola yang sangat sah di Go untuk server, daemon, dan worker:

// Server loop — berjalan selamanya menunggu request
for {
    conn, err := listener.Accept()
    if err != nil {
        log.Println("accept error:", err)
        continue
    }
    go handleConnection(conn)
}

// Worker loop — memproses job dari channel
for {
    select {
    case job := <-jobQueue:
        processJob(job)
    case <-stopSignal:
        return  // keluar dari infinite loop
    }
}

for-range — Iterasi Koleksi #

range adalah cara idiomatik Go untuk mengiterasi koleksi. Ia mengembalikan dua nilai: index (atau key) dan value. Kamu bisa mengabaikan salah satunya dengan blank identifier _.

Range pada Slice dan Array #

buah := []string{"apel", "mangga", "jeruk", "durian"}

// Dua nilai: index dan value
for i, v := range buah {
    fmt.Printf("[%d] %s\n", i, v)
}

// Hanya value — index diabaikan
for _, v := range buah {
    fmt.Println(v)
}

// Hanya index — value diabaikan
for i := range buah {
    fmt.Printf("index: %d\n", i)
}

// Modifikasi elemen via index (range memberi SALINAN value)
angka := []int{1, 2, 3, 4, 5}
for i := range angka {
    angka[i] *= 2  // ✓ modifikasi lewat index, bukan lewat v
}
fmt.Println(angka)  // [2 4 6 8 10]

range memberikan salinan (copy) dari value, bukan reference. Memodifikasi v dalam loop tidak mempengaruhi slice asli:

angka := []int{1, 2, 3}
for _, v := range angka {
    v *= 2  // ✗ memodifikasi salinan lokal, bukan elemen asli
}
fmt.Println(angka)  // [1 2 3] — tidak berubah!

// BENAR: modifikasi lewat index
for i := range angka {
    angka[i] *= 2  // ✓
}
fmt.Println(angka)  // [2 4 6]

Range pada String — Per Rune, Bukan Per Byte #

Ini salah satu perilaku yang paling sering mengejutkan. range pada string mengiterasi per rune (karakter Unicode), bukan per byte. Index yang dikembalikan adalah posisi byte, bukan posisi karakter:

s := "Hello, 世界"

// range per RUNE
for i, r := range s {
    fmt.Printf("byte index %d: %c (U+%04X)\n", i, r, r)
}
// Output:
// byte index 0: H (U+0048)
// byte index 1: e (U+0065)
// byte index 2: l (U+006C)
// byte index 3: l (U+006C)
// byte index 4: o (U+006F)
// byte index 5: , (U+002C)
// byte index 6:   (U+0020)
// byte index 7: 世 (U+4E16)   ← index 7, karakter selanjutnya di byte 10!
// byte index 10: 界 (U+754C)  ← 世 memakan 3 bytes (7,8,9)

// Iterasi per BYTE (tidak otomatis, harus konversi ke []byte)
for i, b := range []byte(s) {
    fmt.Printf("byte index %d: 0x%02X\n", i, b)
}

Range pada Map #

Range pada map mengembalikan key dan value. Urutan iterasi tidak dijamin — berbeda setiap kali program dijalankan:

harga := map[string]int{
    "apel":   5000,
    "mangga": 8000,
    "jeruk":  4500,
}

// Urutan acak setiap run
for k, v := range harga {
    fmt.Printf("%s: Rp%d\n", k, v)
}

// Jika butuh urutan konsisten, sort keynya dulu
import "sort"

keys := make([]string, 0, len(harga))
for k := range harga {
    keys = append(keys, k)
}
sort.Strings(keys)

for _, k := range keys {
    fmt.Printf("%s: Rp%d\n", k, harga[k])
}
// Output selalu berurutan: apel:5000, jeruk:4500, mangga:8000

Range pada Channel #

Range pada channel membaca nilai sampai channel ditutup dengan close():

ch := make(chan int)

// Producer — mengirim nilai dan menutup channel
go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)  // PENTING: tutup channel agar range berhenti
}()

// Consumer — range berhenti saat channel ditutup
for v := range ch {
    fmt.Println(v)  // 0 1 2 3 4
}

break dan continue #

break — Keluar dari Loop #

break menghentikan loop sepenuhnya dan melanjutkan eksekusi setelah blok loop:

// Cari elemen pertama yang memenuhi kondisi
target := 7
angka := []int{3, 1, 4, 1, 5, 9, 2, 6, 7, 3}

found := -1
for i, v := range angka {
    if v == target {
        found = i
        break  // hentikan segera setelah ditemukan
    }
}

if found >= 0 {
    fmt.Printf("Ditemukan di index %d\n", found)
}

continue — Lewati Iterasi Saat Ini #

continue melewatkan sisa body loop untuk iterasi saat ini dan langsung ke iterasi berikutnya:

// Proses hanya elemen yang memenuhi kriteria
for i := 0; i < 10; i++ {
    if i%2 == 0 {
        continue  // lewati bilangan genap
    }
    fmt.Println(i)  // hanya mencetak bilangan ganjil: 1 3 5 7 9
}

// Filter elemen dalam slice
data := []string{"apel", "", "mangga", "  ", "jeruk"}
var hasil []string
for _, s := range data {
    s = strings.TrimSpace(s)
    if s == "" {
        continue  // lewati string kosong atau hanya whitespace
    }
    hasil = append(hasil, s)
}
fmt.Println(hasil)  // [apel mangga jeruk]

Label untuk Nested Loop #

Ketika for bersarang, break dan continue tanpa label hanya mempengaruhi loop terdalam. Untuk mempengaruhi loop luar, gunakan label:

// Tanpa label — break hanya keluar dari loop dalam
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            break  // hanya keluar dari loop j
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// Output: (0,0) (1,0) (2,0) — loop i tetap berjalan

fmt.Println()

// Dengan label — break keluar dari loop berlabel (loop luar)
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer  // keluar dari loop i sekaligus
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// Output: (0,0) (0,1) (0,2) (1,0) — berhenti saat i=1, j=1

fmt.Println()

// continue dengan label — lanjut ke iterasi berikutnya loop berlabel
outer2:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            continue outer2  // langsung ke iterasi i berikutnya
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// Output: (0,0) (1,0) (2,0) — j=1 dan j=2 dilewati karena continue outer2

Gotcha: Closure dalam Loop #

Ini adalah bug klasik yang sangat umum di Go — salah satu yang paling sering ditanyakan di interview dan ditanyakan di Stack Overflow. Ketika kamu membuat closure (fungsi anonim) di dalam loop, closure itu berbagi variabel loop yang sama, bukan menyimpan salinan nilainya:

// ANTI-PATTERN: semua goroutine mencetak nilai yang sama
funcs := make([]func(), 3)
for i := 0; i < 3; i++ {
    funcs[i] = func() {
        fmt.Println(i)  // i adalah variabel yang SAMA untuk semua closure
    }
}
for _, f := range funcs {
    f()
}
// Output: 3 3 3  (bukan 0 1 2!)
// Karena pada saat f() dipanggil, loop sudah selesai dan i = 3

// SOLUSI 1: buat salinan variabel per iterasi (shadowing)
for i := 0; i < 3; i++ {
    i := i  // variabel lokal baru yang meng-shadow i loop
    funcs[i] = func() {
        fmt.Println(i)  // capture i yang berbeda setiap iterasi
    }
}

// SOLUSI 2: lewatkan sebagai argumen fungsi
for i := 0; i < 3; i++ {
    funcs[i] = func(n int) func() {
        return func() { fmt.Println(n) }
    }(i)  // i dievaluasi saat ini, nilai-nya disalin ke n
}

// SOLUSI 3 (paling umum untuk goroutine): lewat parameter
for i := 0; i < 3; i++ {
    go func(n int) {
        fmt.Println(n)  // n adalah salinan per pemanggilan
    }(i)
}
Mulai Go 1.22 (Februari 2024), perilaku loop variable telah berubah: setiap iterasi loop membuat variabel baru, sehingga masalah closure di atas tidak terjadi lagi secara default untuk kode yang dikompilasi dengan Go 1.22+. Namun penting memahami pola ini untuk membaca dan memaintain kode Go versi lama.

Pola Idiomatik #

Loop dengan Index Terbalik #

s := []int{1, 2, 3, 4, 5}

// Iterasi dari belakang ke depan
for i := len(s) - 1; i >= 0; i-- {
    fmt.Println(s[i])
}
// Output: 5 4 3 2 1

// Hapus elemen dari slice sambil iterasi — harus dari belakang
for i := len(s) - 1; i >= 0; i-- {
    if s[i]%2 == 0 {
        s = append(s[:i], s[i+1:]...)  // hapus elemen genap
    }
}

Retry Loop dengan Exponential Backoff #

func withRetry(maxAttempts int, fn func() error) error {
    var lastErr error
    for attempt := 1; attempt <= maxAttempts; attempt++ {
        lastErr = fn()
        if lastErr == nil {
            return nil  // berhasil
        }

        if attempt == maxAttempts {
            break  // jangan tidur setelah percobaan terakhir
        }

        // Exponential backoff: 1s, 2s, 4s, ...
        waitTime := time.Duration(1<<uint(attempt-1)) * time.Second
        fmt.Printf("Attempt %d gagal, retry dalam %v: %v\n",
            attempt, waitTime, lastErr)
        time.Sleep(waitTime)
    }
    return fmt.Errorf("gagal setelah %d percobaan: %w", maxAttempts, lastErr)
}

Sliding Window #

// Hitung rata-rata bergerak (moving average) dengan window size 3
func movingAverage(data []float64, window int) []float64 {
    if len(data) < window {
        return nil
    }

    result := make([]float64, 0, len(data)-window+1)
    sum := 0.0

    // Hitung sum untuk window pertama
    for i := 0; i < window; i++ {
        sum += data[i]
    }
    result = append(result, sum/float64(window))

    // Geser window: tambah elemen baru, hapus elemen lama
    for i := window; i < len(data); i++ {
        sum += data[i] - data[i-window]
        result = append(result, sum/float64(window))
    }
    return result
}

Two-Pointer Pattern #

// Cek apakah slice adalah palindrome
func isPalindrome(s []int) bool {
    left, right := 0, len(s)-1
    for left < right {
        if s[left] != s[right] {
            return false
        }
        left++
        right--
    }
    return true
}

fmt.Println(isPalindrome([]int{1, 2, 3, 2, 1}))  // true
fmt.Println(isPalindrome([]int{1, 2, 3, 4, 5}))  // false

Batch Processing #

// Proses data dalam batch untuk menghindari memory spike
func processBatch(items []Item, batchSize int, fn func([]Item) error) error {
    for i := 0; i < len(items); i += batchSize {
        end := i + batchSize
        if end > len(items) {
            end = len(items)  // batch terakhir mungkin lebih kecil
        }

        batch := items[i:end]
        if err := fn(batch); err != nil {
            return fmt.Errorf("gagal proses batch %d-%d: %w", i, end, err)
        }
        fmt.Printf("Selesai batch %d-%d (%d items)\n", i, end, len(batch))
    }
    return nil
}

Perbandingan dengan Bahasa Lain #

Untuk developer yang datang dari bahasa lain, berikut pemetaan pola loop yang familiar ke Go:

// JAVA/C#: while
while (condition) { body }
// GO:
for condition { body }

// JAVA/C#: do-while
do { body } while (condition);
// GO: tidak ada padanan langsung — gunakan:
for {
    body
    if !condition { break }
}

// PYTHON: for item in list
for item in items:
    body
// GO:
for _, item := range items { body }

// JAVA: for (int i = 0; i < n; i++)
for (int i = 0; i < n; i++) { body }
// GO:
for i := 0; i < n; i++ { body }

// PYTHON: enumerate
for i, item in enumerate(items):
    body
// GO:
for i, item := range items { body }

// PYTHON: zip
for a, b in zip(list1, list2):
    body
// GO: tidak ada zip bawaan — gunakan index
for i := 0; i < len(list1) && i < len(list2); i++ {
    a, b := list1[i], list2[i]
    body
}

Contoh Program Lengkap #

Program berikut mensimulasikan sistem analitik sederhana yang memproses data penjualan menggunakan berbagai pola loop:

package main

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

type Sale struct {
    Product  string
    Category string
    Amount   float64
    Month    int
}

// Hitung statistik dasar menggunakan berbagai pola loop
func analyzesSales(sales []Sale) {
    if len(sales) == 0 {
        fmt.Println("Tidak ada data penjualan")
        return
    }

    // ── Pola 1: Classic for — hitung total
    total := 0.0
    for i := 0; i < len(sales); i++ {
        total += sales[i].Amount
    }

    // ── Pola 2: for-range — kumpulkan per kategori
    perKategori := make(map[string]float64)
    for _, s := range sales {
        perKategori[s.Category] += s.Amount
    }

    // ── Pola 3: for-range map dengan sort untuk output konsisten
    fmt.Printf("=== Analitik Penjualan ===\n")
    fmt.Printf("Total Pendapatan: Rp%.0f\n\n", total)

    kategori := make([]string, 0, len(perKategori))
    for k := range perKategori {
        kategori = append(kategori, k)
    }
    sort.Strings(kategori)

    fmt.Println("Penjualan per Kategori:")
    for _, k := range kategori {
        pct := perKategori[k] / total * 100
        bar := strings.Repeat("█", int(pct/5))
        fmt.Printf("  %-15s Rp%8.0f  %5.1f%%  %s\n",
            k, perKategori[k], pct, bar)
    }

    // ── Pola 4: for-range dengan continue — filter data
    fmt.Println("\nPenjualan > Rp1.000.000:")
    count := 0
    for _, s := range sales {
        if s.Amount <= 1_000_000 {
            continue  // lewati yang kecil
        }
        fmt.Printf("  %-20s Rp%.0f\n", s.Product, s.Amount)
        count++
    }
    if count == 0 {
        fmt.Println("  (tidak ada)")
    }

    // ── Pola 5: for-range dengan break — cari penjualan terbesar
    sort.Slice(sales, func(i, j int) bool {
        return sales[i].Amount > sales[j].Amount
    })

    fmt.Println("\nTop 3 Penjualan Terbesar:")
    for i, s := range sales {
        if i >= 3 {
            break  // hanya ambil 3 teratas
        }
        fmt.Printf("  %d. %-20s Rp%.0f\n", i+1, s.Product, s.Amount)
    }

    // ── Pola 6: loop terbalik — tampilkan 3 penjualan terkecil
    fmt.Println("\nTop 3 Penjualan Terkecil:")
    for i := len(sales) - 1; i >= len(sales)-3 && i >= 0; i-- {
        rank := len(sales) - i
        fmt.Printf("  %d. %-20s Rp%.0f\n", rank, sales[i].Product, sales[i].Amount)
    }

    // ── Pola 7: nested loop dengan label — cari pola per bulan per kategori
    bulan := []int{1, 2, 3}
    kats := []string{"Elektronik", "Fashion"}

    fmt.Println("\nPenjualan per Bulan dan Kategori:")
    outer:
    for _, bln := range bulan {
        for _, kat := range kats {
            subtotal := 0.0
            for _, s := range sales {
                if s.Month == bln && s.Category == kat {
                    subtotal += s.Amount
                }
            }
            if subtotal > 0 {
                fmt.Printf("  Bulan %d | %-15s Rp%.0f\n", bln, kat, subtotal)
            }
        }
        if bln == 2 {
            fmt.Println("  (hanya menampilkan data s/d bulan 2)")
            break outer
        }
    }

    // ── Pola 8: moving average untuk tren
    perBulan := make([]float64, 13)  // index 1-12 untuk bulan 1-12
    for _, s := range sales {
        if s.Month >= 1 && s.Month <= 12 {
            perBulan[s.Month] += s.Amount
        }
    }

    windowSize := 3
    fmt.Printf("\nMoving Average %d Bulan:\n", windowSize)
    for i := windowSize; i <= 12; i++ {
        sum := 0.0
        for j := i - windowSize + 1; j <= i; j++ {
            sum += perBulan[j]
        }
        avg := sum / float64(windowSize)
        if avg > 0 {
            fmt.Printf("  Bulan %2d (avg %d bln): Rp%.0f\n", i, windowSize, avg)
        }
    }

    // ── Pola 9: infinite loop untuk simulasi retry
    fmt.Println("\nSimulasi Koneksi Database:")
    maxRetry := 3
    connected := false
    attempt := 0
    for {
        attempt++
        // Simulasi: berhasil di percobaan ke-2
        if attempt == 2 {
            connected = true
            fmt.Printf("  Attempt %d: berhasil terhubung!\n", attempt)
            break
        }
        fmt.Printf("  Attempt %d: gagal, retry...\n", attempt)
        if attempt >= maxRetry {
            fmt.Println("  Menyerah setelah", maxRetry, "percobaan")
            break
        }
    }
    _ = connected

    // ── Statistik akhir
    amounts := make([]float64, len(sales))
    for i, s := range sales {
        amounts[i] = s.Amount
    }

    mean := total / float64(len(sales))
    variance := 0.0
    for _, a := range amounts {
        diff := a - mean
        variance += diff * diff
    }
    variance /= float64(len(sales))
    stddev := math.Sqrt(variance)

    fmt.Printf("\nStatistik:\n")
    fmt.Printf("  Jumlah transaksi: %d\n", len(sales))
    fmt.Printf("  Rata-rata       : Rp%.0f\n", mean)
    fmt.Printf("  Std Deviasi     : Rp%.0f\n", stddev)
}

func main() {
    sales := []Sale{
        {"Laptop Pro",     "Elektronik", 15_000_000, 1},
        {"Kaos Polos",     "Fashion",       250_000, 1},
        {"Earphone BT",    "Elektronik",  1_500_000, 1},
        {"Celana Jeans",   "Fashion",       450_000, 2},
        {"Monitor 4K",     "Elektronik",  5_000_000, 2},
        {"Jaket Hoodie",   "Fashion",       380_000, 2},
        {"SSD 1TB",        "Elektronik",  1_200_000, 3},
        {"Kemeja Formal",  "Fashion",       320_000, 3},
        {"Keyboard Mech",  "Elektronik",  2_500_000, 3},
        {"Topi Baseball",  "Fashion",       150_000, 3},
    }

    analyzesSales(sales)
}

Ringkasan #

  • Hanya satu keyword for — Go tidak punya while, do-while, atau foreach; semua pola perulangan diekspresikan dengan for.
  • Tiga bentuk for: classic tiga komponen (init; cond; post), condition-only (setara while), dan infinite loop (for {}).
  • for-range mengembalikan index/key dan value untuk slice, array, map, string, dan channel.
  • range memberikan salinan value — modifikasi v tidak mempengaruhi koleksi asli; gunakan index untuk modifikasi.
  • range pada string mengiterasi per rune (karakter Unicode), bukan per byte; index bisa melompat untuk karakter multi-byte.
  • Urutan range pada map tidak dijamin — sort keynya jika butuh output konsisten.
  • break dan continue dengan label untuk mengontrol loop luar dari dalam nested loop.
  • Closure dalam loop berbagi variabel yang sama — buat salinan dengan i := i atau lewatkan sebagai argumen untuk menghindari bug.
  • Go 1.22+ mengubah semantik loop variable sehingga closure dalam loop tidak lagi mengalami masalah ini secara default.
  • Infinite loop dengan for {} adalah pola yang sah untuk server, daemon, dan worker — hentikan dengan break, return, atau signal dari channel.

← Sebelumnya: Seleksi Kondisi   Berikutnya: Fungsi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact