Perulangan #

Perulangan adalah mekanisme untuk mengeksekusi blok kode secara berulang — baik dengan jumlah iterasi yang sudah diketahui, maupun selama kondisi tertentu terpenuhi. TypeScript mewarisi seluruh konstruksi perulangan dari JavaScript, tapi menambahkan type safety yang membuat banyak jenis bug klasik — seperti mengakses indeks yang salah atau memanggil metode yang tidak ada pada elemen — terdeteksi saat kompilasi, bukan saat runtime. Lebih dari itu, TypeScript sangat mendukung gaya pemrograman fungsional dengan metode array seperti map, filter, dan reduce yang lebih aman dan ekspresif dari loop imperatif tradisional. Artikel ini membahas semua bentuk perulangan di TypeScript beserta kapan menggunakan masing-masing secara tepat.

for — Perulangan dengan Indeks #

Perulangan for klasik memberikan kontrol penuh atas inisialisasi, kondisi, dan ekspresi per iterasi. Ia paling berguna saat kamu membutuhkan akses ke indeks, perlu iterasi mundur, atau perlu melewati elemen dengan langkah tertentu.

// Perulangan maju standar
for (let i = 0; i < 5; i++) {
  console.log(`Iterasi ke-${i}`);
}

// Iterasi mundur — berguna untuk menghapus elemen dari array saat iterasi
const tugas: string[] = ["Belajar", "Kerja", "Olahraga", "Tidur"];
for (let i = tugas.length - 1; i >= 0; i--) {
  console.log(`${i + 1}. ${tugas[i]}`);
}

// Langkah dua per iterasi
for (let i = 0; i <= 10; i += 2) {
  console.log(i); // 0, 2, 4, 6, 8, 10
}

Type Safety pada Iterasi Array #

TypeScript menjamin tipe elemen yang diakses lewat indeks sesuai dengan tipe array:

const harga: number[] = [15_000, 25_000, 10_000, 45_000];

let total = 0;
for (let i = 0; i < harga.length; i++) {
  total += harga[i]; // harga[i] bertipe number — aman
  // total += harga[i].toUpperCase(); // ✗ Error: 'toUpperCase' tidak ada di 'number'
}
console.log(`Total: Rp ${total.toLocaleString("id-ID")}`);

// Array of object — TypeScript mengetahui tipe setiap properti
interface Produk {
  nama: string;
  harga: number;
  stok: number;
}

const katalog: Produk[] = [
  { nama: "Kurma Ajwa", harga: 85_000, stok: 50 },
  { nama: "Madu Sidr", harga: 250_000, stok: 12 },
  { nama: "Minyak Zaitun", harga: 75_000, stok: 30 },
];

for (let i = 0; i < katalog.length; i++) {
  const produk = katalog[i]; // Tipe: Produk
  console.log(`${produk.nama}: Rp ${produk.harga.toLocaleString("id-ID")} (${produk.stok} tersisa)`);
}

Jebakan Indeks Out-of-Bounds #

TypeScript dengan konfigurasi default tidak mendeteksi akses indeks yang di luar batas array — nilai yang dikembalikan adalah undefined bukan error kompilasi. Aktifkan noUncheckedIndexedAccess di tsconfig.json untuk perlindungan lebih:

const angka: number[] = [1, 2, 3];

// Tanpa noUncheckedIndexedAccess — TypeScript menganggap ini selalu number
const nilai = angka[10]; // Tipe: number — padahal nilainya undefined di runtime!

// Dengan noUncheckedIndexedAccess: true di tsconfig
// const nilai = angka[10]; // Tipe: number | undefined — lebih akurat dan aman

while — Perulangan Berbasis Kondisi #

while mengeksekusi blok kode selama kondisinya true. Paling cocok ketika jumlah iterasi tidak diketahui di awal dan bergantung pada kondisi yang bisa berubah di dalam loop.

// Simulasi pengiriman dengan retry
async function kirimDenganRetry(
  data: string,
  maksPencobaan: number
): Promise<boolean> {
  let percobaan = 0;

  while (percobaan < maksPencobaan) {
    percobaan++;
    console.log(`Percobaan ${percobaan}/${maksPencobaan}...`);

    try {
      await kirimData(data); // Asumsikan fungsi async ini ada
      console.log("Berhasil dikirim!");
      return true;
    } catch (err) {
      console.warn(`Gagal: ${(err as Error).message}`);
      if (percobaan < maksPencobaan) {
        await tunggu(1000 * percobaan); // Exponential backoff sederhana
      }
    }
  }

  console.error("Semua percobaan gagal");
  return false;
}

// Helper
function tunggu(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

Guard Infinite Loop #

while rentan terhadap infinite loop jika kondisi tidak pernah menjadi false. Selalu pastikan ada sesuatu di dalam loop yang akan mengubah kondisi menjadi false pada akhirnya:

// ANTI-PATTERN: Kondisi tidak pernah berubah — infinite loop
// let aktif = true;
// while (aktif) {
//   prosesData(); // Jika prosesData tidak mengubah 'aktif', loop tidak pernah berhenti
// }

// BENAR: Kondisi pasti berubah, atau ada break condition
let antrean: string[] = ["tugas-1", "tugas-2", "tugas-3"];
let batasWaktu = Date.now() + 5000; // Batas 5 detik

while (antrean.length > 0 && Date.now() < batasWaktu) {
  const tugas = antrean.shift()!; // Ambil dari depan
  proseskanTugas(tugas);
}

if (antrean.length > 0) {
  console.warn(`${antrean.length} tugas belum selesai karena waktu habis`);
}

function proseskanTugas(tugas: string): void {
  console.log(`Memproses: ${tugas}`);
}

do...while — Eksekusi Minimal Satu Kali #

do...while menjamin blok kode dieksekusi setidaknya satu kali sebelum kondisi dievaluasi. Berguna untuk skenario seperti meminta input pengguna atau polling yang harus berjalan minimal sekali.

// Simulasi menu interaktif yang terus muncul hingga pengguna keluar
function tampilkanMenu(): string {
  // Dalam konteks Node.js, ini bisa menggunakan readline
  // Di sini kita simulasikan dengan array pilihan
  const pilihan = ["1", "2", "3", "keluar"];
  return pilihan[Math.floor(Math.random() * pilihan.length)];
}

let pilihan: string;
do {
  pilihan = tampilkanMenu();
  console.log(`Pilihan: ${pilihan}`);

  switch (pilihan) {
    case "1": console.log("Membuka profil..."); break;
    case "2": console.log("Membuka pengaturan..."); break;
    case "3": console.log("Membuka bantuan..."); break;
  }
} while (pilihan !== "keluar");

console.log("Sampai jumpa!");

Perbandingan while vs do...while #

while:
  Cek kondisi DULU → eksekusi jika true
  Bisa 0 kali eksekusi jika kondisi langsung false

do...while:
  Eksekusi DULU → cek kondisi setelahnya
  Minimal 1 kali eksekusi, kondisi apapun

for...of — Iterasi Nilai (Pilihan Utama untuk Array) #

for...of adalah cara paling idiomatik dan aman untuk mengiterasi elemen array, string, Set, Map, dan objek iterable lainnya di TypeScript modern. TypeScript menyimpulkan tipe elemen secara otomatis:

const buah: string[] = ["mangga", "pisang", "jeruk", "apel"];

// TypeScript tahu 'buah' bertipe string di setiap iterasi
for (const b of buah) {
  console.log(b.toUpperCase()); // ✓ .toUpperCase() tersedia karena tipe string
}

// for...of pada array of object — tipe lengkap tersedia
const karyawan: Array<{ nama: string; departemen: string; gaji: number }> = [
  { nama: "Budi", departemen: "Engineering", gaji: 15_000_000 },
  { nama: "Siti", departemen: "Design", gaji: 12_000_000 },
  { nama: "Ahmad", departemen: "Engineering", gaji: 18_000_000 },
];

for (const k of karyawan) {
  // k bertipe { nama: string; departemen: string; gaji: number }
  console.log(`${k.nama} (${k.departemen}): Rp ${k.gaji.toLocaleString("id-ID")}`);
}

for...of dengan entries() — Nilai dan Indeks Sekaligus #

Saat kamu butuh indeks sekaligus nilai, gunakan .entries():

const daftarTugas: string[] = ["Desain UI", "Implementasi API", "Testing", "Deploy"];

for (const [indeks, tugas] of daftarTugas.entries()) {
  // indeks: number, tugas: string — TypeScript menyimpulkan keduanya
  console.log(`${indeks + 1}. ${tugas}`);
}
// 1. Desain UI
// 2. Implementasi API
// 3. Testing
// 4. Deploy

for...of pada String, Set, dan Map #

// Iterasi karakter string
const teks = "TypeScript";
for (const karakter of teks) {
  process.stdout.write(karakter + " "); // T y p e S c r i p t
}

// Iterasi Set — setiap nilai unik
const tagUnik = new Set<string>(["typescript", "nodejs", "typescript", "golang"]);
for (const tag of tagUnik) {
  console.log(tag); // typescript, nodejs, golang (duplikat dihapus)
}

// Iterasi Map — setiap pasangan [kunci, nilai]
const konfigurasi = new Map<string, string>([
  ["host", "localhost"],
  ["port", "5432"],
  ["nama_db", "muslimapps"],
]);

for (const [kunci, nilai] of konfigurasi) {
  console.log(`${kunci} = ${nilai}`);
}

for...in — Iterasi Kunci Object (Hati-hati!) #

for...in mengiterasi semua properti yang enumerable dari sebuah object, termasuk properti yang diwarisi dari prototype chain. Ini jarang yang kamu inginkan untuk array.

// ANTI-PATTERN: Menggunakan for...in untuk array
const angka: number[] = [10, 20, 30];
for (const kunci in angka) {
  console.log(kunci); // "0", "1", "2" — kunci bertipe string, bukan number!
  console.log(angka[kunci]); // Ini bekerja tapi tidak type-safe
}
// Jika ada library yang menambahkan properti ke Array.prototype,
// for...in juga akan mengiterasi properti tambahan itu — bug yang halus!

// BENAR: for...of untuk array, for...in untuk plain object
const pengaturan: Record<string, string | number> = {
  tema: "gelap",
  bahasa: "id",
  ukuranFont: 14,
};

for (const kunci in pengaturan) {
  // kunci bertipe string
  if (Object.prototype.hasOwnProperty.call(pengaturan, kunci)) {
    // hasOwnProperty memastikan kita hanya iterasi properti sendiri,
    // bukan yang diwarisi dari prototype
    console.log(`${kunci}: ${pengaturan[kunci]}`);
  }
}
Untuk array, selalu gunakan for...of bukan for...in. for...in mengembalikan kunci sebagai string (bukan number), mengiterasi properti yang diwarisi dari prototype, dan urutannya tidak dijamin konsisten di semua engine JavaScript. Gunakan for...in hanya untuk plain object, dan selalu sertakan pengecekan hasOwnProperty.

Metode Array Fungsional — Alternatif yang Lebih Ekspresif #

Dalam TypeScript modern, banyak kasus perulangan array bisa digantikan oleh metode fungsional bawaan array yang lebih ekspresif, lebih mudah di-chain, dan lebih mudah diuji. TypeScript memberikan type inference penuh pada semua metode ini.

map — Transformasi Elemen #

const hargaAsli: number[] = [100_000, 250_000, 75_000, 180_000];

// ANTI-PATTERN: for loop untuk transformasi
const hargaDiskon1: number[] = [];
for (const h of hargaAsli) {
  hargaDiskon1.push(h * 0.9);
}

// BENAR: map — lebih ringkas, return type otomatis disimpulkan
const hargaDiskon2 = hargaAsli.map((h) => h * 0.9);
// Tipe: number[]

// Map dengan object
interface Pengguna { id: number; nama: string; aktif: boolean }

const pengguna: Pengguna[] = [
  { id: 1, nama: "Budi", aktif: true },
  { id: 2, nama: "Siti", aktif: false },
  { id: 3, nama: "Ahmad", aktif: true },
];

// Transformasi ke format ringkas untuk tampilan
const ringkasan = pengguna.map(({ id, nama, aktif }) => ({
  label: `${id}: ${nama}`,
  warna: aktif ? "hijau" : "merah",
}));
// Tipe: Array<{ label: string; warna: string }>

filter — Menyaring Elemen #

const semuaProduk: Produk[] = [
  { nama: "Kurma", harga: 85_000, stok: 0 },
  { nama: "Madu", harga: 250_000, stok: 12 },
  { nama: "Minyak", harga: 75_000, stok: 30 },
  { nama: "Kismis", harga: 45_000, stok: 0 },
];

// Filter produk yang tersedia — tipe hasil tetap Produk[]
const produkTersedia = semuaProduk.filter((p) => p.stok > 0);
// [{ nama: "Madu", ... }, { nama: "Minyak", ... }]

// Filter dengan type guard — menyempitkan tipe hasil
type NilaiMungkinNull = string | null | undefined;
const campuran: NilaiMungkinNull[] = ["satu", null, "dua", undefined, "tiga"];

// Tanpa type guard — hasil bertipe (string | null | undefined)[]
const tanpaGuard = campuran.filter((v) => v !== null && v !== undefined);

// Dengan type guard — hasil bertipe string[] yang lebih spesifik
const denganGuard = campuran.filter((v): v is string => v !== null && v !== undefined);
// Tipe: string[] — TypeScript tahu semua null/undefined sudah tersaring

reduce — Akumulasi Nilai #

const transaksi: Array<{ jenis: "masuk" | "keluar"; jumlah: number }> = [
  { jenis: "masuk", jumlah: 5_000_000 },
  { jenis: "keluar", jumlah: 1_500_000 },
  { jenis: "masuk", jumlah: 2_000_000 },
  { jenis: "keluar", jumlah: 750_000 },
];

// Hitung saldo akhir
const saldo = transaksi.reduce((akumulasi, t) => {
  return t.jenis === "masuk"
    ? akumulasi + t.jumlah
    : akumulasi - t.jumlah;
}, 0); // Nilai awal: 0

console.log(`Saldo: Rp ${saldo.toLocaleString("id-ID")}`); // Rp 4.750.000

// Reduce untuk mengelompokkan data (group by)
const perDepartemen = karyawan.reduce(
  (kelompok, k) => {
    const dept = k.departemen;
    kelompok[dept] = kelompok[dept] ?? [];
    kelompok[dept].push(k.nama);
    return kelompok;
  },
  {} as Record<string, string[]>
);
// { Engineering: ["Budi", "Ahmad"], Design: ["Siti"] }

Chaining Metode Array #

Metode array bisa di-chain satu sama lain untuk pipeline pemrosesan data yang ekspresif:

const laporanBulanan = katalog
  .filter((p) => p.stok > 0)               // Hanya yang tersedia
  .map((p) => ({                            // Transformasi ke format laporan
    nama: p.nama,
    pendapatan: p.harga * p.stok,
  }))
  .sort((a, b) => b.pendapatan - a.pendapatan) // Urutkan dari tertinggi
  .slice(0, 3);                             // Ambil top 3

// TypeScript menyimpulkan tipe akhir: Array<{ nama: string; pendapatan: number }>

break dan continue — Kontrol Alur Loop #

break menghentikan loop sepenuhnya, continue melewati sisa iterasi saat ini dan melanjutkan ke iterasi berikutnya.

// break — keluar dari loop saat kondisi terpenuhi
const daftar = [3, 7, 2, 9, 1, 5, 8];
let posisiPertama = -1;

for (let i = 0; i < daftar.length; i++) {
  if (daftar[i] > 6) {
    posisiPertama = i;
    break; // Hentikan pencarian segera setelah menemukan yang pertama
  }
}
console.log(`Posisi pertama nilai > 6: ${posisiPertama}`); // 1

// continue — lewati elemen yang tidak memenuhi syarat
const nilaiMahasiswa = [75, 45, 90, 30, 85, 60, 40];
const nilaiLulus: number[] = [];

for (const nilai of nilaiMahasiswa) {
  if (nilai < 60) continue; // Lewati nilai di bawah 60
  nilaiLulus.push(nilai);   // Hanya proses nilai >= 60
}
console.log(nilaiLulus); // [75, 90, 85, 60]

// Label untuk break/continue pada nested loop
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      break outer; // Keluar dari KEDUA loop sekaligus
    }
    console.log(`i=${i}, j=${j}`);
  }
}
// i=0,j=0 | i=0,j=1 | i=0,j=2 | i=1,j=0 — lalu berhenti

Generator — Iterasi Kustom #

Generator adalah fungsi yang bisa “dijeda” di tengah eksekusi menggunakan yield. Ia menghasilkan nilai satu per satu dan sangat efisien untuk dataset besar atau urutan nilai yang tidak terbatas:

// Generator function — ditandai dengan tanda bintang (*)
function* urutanFibonacci(): Generator<number> {
  let [a, b] = [0, 1];
  while (true) {
    yield a;          // "Kirim" nilai a, lalu jeda
    [a, b] = [b, a + b];
  }
}

// Gunakan generator dengan for...of — TypeScript tahu setiap nilai bertipe number
const fib = urutanFibonacci();
for (const angka of fib) {
  if (angka > 100) break; // Hentikan saat melewati 100
  process.stdout.write(angka + " ");
}
// 0 1 1 2 3 5 8 13 21 34 55 89

// Generator untuk range seperti Python
function* range(mulai: number, selesai: number, langkah = 1): Generator<number> {
  for (let i = mulai; i < selesai; i += langkah) {
    yield i;
  }
}

for (const i of range(0, 10, 2)) {
  process.stdout.write(i + " "); // 0 2 4 6 8
}

Memilih Konstruksi Perulangan yang Tepat #

flowchart TD
    A{"Apa yang ingin<br/>kamu iterasi?"} --> B["Array atau koleksi<br/>dengan nilai"]
    A --> C["Object dengan<br/>kunci-nilai"]
    A --> D["Kondisi dinamis<br/>jumlah iterasi tidak pasti"]
    A --> E["Dataset besar atau<br/>urutan tak terbatas"]

    B --> F{"Perlu transformasi<br/>atau filtering?"}
    F -- "Ya" --> G["map / filter / reduce<br/>atau chaining"]
    F -- "Tidak" --> H{"Perlu indeks?"}

    H -- "Ya" --> I["for of dengan entries()"]
    H -- "Tidak" --> J["for of"]

    C --> K["for in dengan<br/>hasOwnProperty<br/>atau Object.entries()"]

    D --> L{"Harus jalan<br/>minimal sekali?"}
    L -- "Ya" --> M["do while"]
    L -- "Tidak" --> N["while"]

    E --> O["Generator function<br/>dengan yield"]

    style G fill:#51cf66,color:#fff
    style I fill:#51cf66,color:#fff
    style J fill:#51cf66,color:#fff
    style K fill:#fcc419,color:#000
    style M fill:#339af0,color:#fff
    style N fill:#339af0,color:#fff
    style O fill:#cc5de8,color:#fff

Ringkasan #

  • for...of adalah pilihan utama untuk array — ia type-safe, ringkas, dan tidak mengalami masalah scoping seperti for dengan var; gunakan .entries() saat butuh indeks sekaligus.
  • Hindari for...in untuk array — ia mengembalikan kunci sebagai string, mengiterasi properti prototype, dan tidak menjamin urutan; gunakan for...in hanya untuk plain object dengan pengecekan hasOwnProperty.
  • map, filter, reduce lebih ekspresif dari loop imperatif untuk transformasi data — hasilnya lebih mudah di-chain, immutable, dan type inference-nya bekerja secara otomatis.
  • filter dengan type guard ((v): v is T) menghasilkan array dengan tipe yang lebih spesifik, bukan hanya menyaring elemen — ini penting saat bekerja dengan union type yang mengandung null/undefined.
  • for klasik masih relevan untuk iterasi mundur, langkah non-standard, atau saat kamu perlu memodifikasi array di tempat selama iterasi.
  • while untuk kondisi dinamis — kondisi yang baru diketahui di runtime; selalu pastikan ada sesuatu dalam loop yang akan membuat kondisi menjadi false, atau tambahkan batas iterasi maksimum.
  • do...while untuk minimal satu eksekusi — cocok untuk skenario polling, menu interaktif, atau validasi input yang harus dicoba setidaknya sekali.
  • Generator untuk dataset besar atau urutan tak terbatas — ia menghemat memori dengan menghasilkan nilai satu per satu (lazy evaluation), bukan menyimpan seluruh koleksi di memori sekaligus.
  • break dan continue mengontrol alur loop; gunakan label (outer:) pada nested loop untuk keluar dari lebih dari satu level sekaligus.
  • Aktifkan noUncheckedIndexedAccess di tsconfig.json untuk mendapatkan peringatan saat akses array dengan indeks menghasilkan tipe T | undefined — lebih aman dari default T.

← Sebelumnya: Seleksi Kondisi   Berikutnya: Fungsi →

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