SEJATIDIMEDIA Logo
Tersedia Sesi Konsultasi Gratis
SejatiDimedia Logo

Generics, dari Dasar sampai Constraint

Generics sering dianggap topik yang menakutkan buat pemula, padahal konsepnya sederhana banget kalau dijelasin pake analogi yang pas. Artikel ini mulai dari masalah nyata yang diselesain generics, yaitu bikin fungsi atau komponen yang reusable tanpa kehilangan informasi tipe. Pembaca bakal diajak bangun pemahaman step by step, dari generic function sederhana, generic interface, sampai penggunaan `extends` buat batasin tipe apa aja yang boleh masuk, dan default type parameter biar generic lebih fleksibel. Semua dijelasin pake contoh kode dunia nyata kayak fungsi fetch data API dan komponen wrapper.

Timur Dian Radha Sejati

Timur Dian Radha Sejati

Founder SejatiDimedia•25 September 2026•5 menit baca
Silabus Seri Rekayasa

TypeScript Fundamentals

Series ini dirancang untuk membangun fondasi TypeScript yang kuat bagi pembaca yang baru mulai belajar atau ingin memperkuat pemahaman dasarnya. Dimulai dari alasan mendasar kenapa TypeScript layak dipelajari dibanding JavaScript murni, lanjut ke konsep-konsep inti seperti perbedaan any, unknown, dan never, cara kerja type inference, union dan intersection type, generics, hingga utility types yang paling sering dipakai di proyek nyata. Setiap artikel ditulis dengan pendekatan step by step, disertai contoh kode dan studi kasus dunia nyata, sehingga pembaca tidak hanya tahu "apa" tapi juga paham "kenapa" di balik setiap konsep. Series ini menjadi pondasi sebelum masuk ke topik yang lebih advance seperti type level programming dan pola arsitektur TypeScript di Series berikutnya.

Lihat Silabus Lengkap
Generics, dari Dasar sampai Constraint

Coba bayangin kamu punya kotak perkakas dengan satu kunci pas yang bentuknya bisa nyesuain sendiri, entah itu buat baut kecil, baut gede, atau bentuk yang aneh sekalipun. Satu alat, tapi tetep pas dipake buat banyak kebutuhan berbeda, tanpa kamu harus punya kunci pas terpisah buat tiap ukuran. Nah, generics di TypeScript itu kurang lebih kayak gitu, satu fungsi atau tipe yang bisa "nyesuain diri" sesuai tipe data yang kamu kasih, tapi tetep type safe.

Masalah yang Diselesain Generics

Bayangin kamu bikin fungsi sederhana buat ngambil elemen pertama dari sebuah array.

index.tstypescript
1
function getFirst(arr: any[]) {
2
return arr[0];
3
}
4
 
5
const angka = getFirst([1, 2, 3]); // tipe: any
6
const nama = getFirst(["Andi", "Budi"]); // tipe: any
6 baris•153 chars
UTF-8•Spaces: 4

Fungsi ini emang jalan, tapi ada masalah gede. Hasil return-nya selalu

PLAINTEXT Snippetplaintext
1
any
1 baris•3 chars
UTF-8•Spaces: 4
, gak peduli apa isi array yang kamu masukin. Padahal secara logika, kalau kamu masukin array angka, harusnya hasilnya juga bertipe
PLAINTEXT Snippetplaintext
1
number
1 baris•6 chars
UTF-8•Spaces: 4
. Kalau kamu masukin array string, harusnya hasilnya
PLAINTEXT Snippetplaintext
1
string
1 baris•6 chars
UTF-8•Spaces: 4
. Fungsi di atas gak bisa "inget" hubungan antara tipe input dan tipe output.

Coba solusiin pake generic:

index.tstypescript
1
function getFirst<T>(arr: T[]): T {
2
return arr[0];
3
}
4
 
5
const angka = getFirst([1, 2, 3]); // tipe: number
6
const nama = getFirst(["Andi", "Budi"]); // tipe: string
6 baris•163 chars
UTF-8•Spaces: 4

PLAINTEXT Snippetplaintext
1
T
1 baris•1 chars
UTF-8•Spaces: 4
di sini adalah type parameter, semacam "variabel" tapi buat tipe data, bukan buat nilai. Pas kamu manggil
PLAINTEXT Snippetplaintext
1
getFirst([1, 2, 3])
1 baris•19 chars
UTF-8•Spaces: 4
, TypeScript otomatis nebak
PLAINTEXT Snippetplaintext
1
T
1 baris•1 chars
UTF-8•Spaces: 4
itu
PLAINTEXT Snippetplaintext
1
number
1 baris•6 chars
UTF-8•Spaces: 4
, terus dia tau kalau return value-nya juga pasti
PLAINTEXT Snippetplaintext
1
number
1 baris•6 chars
UTF-8•Spaces: 4
. Pas kamu manggil
PLAINTEXT Snippetplaintext
1
getFirst(["Andi", "Budi"])
1 baris•26 chars
UTF-8•Spaces: 4
,
PLAINTEXT Snippetplaintext
1
T
1 baris•1 chars
UTF-8•Spaces: 4
jadi
PLAINTEXT Snippetplaintext
1
string
1 baris•6 chars
UTF-8•Spaces: 4
, dan return value-nya ikut jadi
PLAINTEXT Snippetplaintext
1
string
1 baris•6 chars
UTF-8•Spaces: 4
.

Ini bedanya generic sama

PLAINTEXT Snippetplaintext
1
any
1 baris•3 chars
UTF-8•Spaces: 4
.
PLAINTEXT Snippetplaintext
1
any
1 baris•3 chars
UTF-8•Spaces: 4
itu ngilangin informasi tipe sama sekali. Generic itu justru mempertahankan hubungan antara tipe input dan output, tapi tetep fleksibel buat dipake dengan tipe data apa aja.

Generic di Interface dan Type

Generic gak cuma bisa dipake di fungsi, tapi juga di interface atau type, biasanya buat bikin struktur data yang reusable.

index.tstypescript
1
interface ApiResponse<T> {
2
status: number;
3
data: T;
4
}
5
 
6
const responseUser: ApiResponse<{ nama: string }> = {
7
status: 200,
8
data: { nama: "Andi" },
9
};
10
 
11
const responseProduk: ApiResponse<{ harga: number }> = {
12
status: 200,
13
data: { harga: 50000 },
14
};
14 baris•258 chars
UTF-8•Spaces: 4

PLAINTEXT Snippetplaintext
1
ApiResponse<T>
1 baris•14 chars
UTF-8•Spaces: 4
di sini bisa dipake buat response API apa aja, tinggal ganti
PLAINTEXT Snippetplaintext
1
T
1 baris•1 chars
UTF-8•Spaces: 4
-nya sesuai bentuk data yang dikembaliin. Kamu gak perlu bikin interface terpisah kayak
PLAINTEXT Snippetplaintext
1
ApiResponseUser
1 baris•15 chars
UTF-8•Spaces: 4
dan
PLAINTEXT Snippetplaintext
1
ApiResponseProduk
1 baris•17 chars
UTF-8•Spaces: 4
yang isinya sebenernya mirip banget, cuma beda di bagian
PLAINTEXT Snippetplaintext
1
data
1 baris•4 chars
UTF-8•Spaces: 4
.

Ini yang bikin generic kepake banget di proyek nyata, apalagi kalau kamu kerja dengan banyak endpoint API yang punya struktur response yang mirip tapi isinya beda-beda.

extends, Ngasih Batasan ke Generic

Kadang kamu gak mau generic-nya terlalu bebas nerima tipe apa aja. Misalnya, kamu bikin fungsi yang butuh properti tertentu ada di objeknya, gak peduli bentuk lengkapnya kayak apa.

index.tstypescript
1
function tampilkanNama<T extends { nama: string }>(item: T) {
2
console.log(item.nama);
3
}
4
 
5
tampilkanNama({ nama: "Andi", umur: 25 }); // aman
6
tampilkanNama({ nama: "Budi" }); // aman
7
tampilkanNama({ umur: 25 }); // Error, gak ada properti nama
7 baris•243 chars
UTF-8•Spaces: 4

Dengan

PLAINTEXT Snippetplaintext
1
T extends { nama: string }
1 baris•26 chars
UTF-8•Spaces: 4
, kamu bilang ke TypeScript, "generic ini boleh berupa tipe apa aja, asal minimal punya properti
PLAINTEXT Snippetplaintext
1
nama
1 baris•4 chars
UTF-8•Spaces: 4
bertipe
PLAINTEXT Snippetplaintext
1
string
1 baris•6 chars
UTF-8•Spaces: 4
". Ini beda sama kalau kamu langsung nulis parameter
PLAINTEXT Snippetplaintext
1
{ nama: string }
1 baris•16 chars
UTF-8•Spaces: 4
tanpa generic, karena
PLAINTEXT Snippetplaintext
1
T
1 baris•1 chars
UTF-8•Spaces: 4
tetep "inget" properti tambahan lain yang dikirim, bukan cuma
PLAINTEXT Snippetplaintext
1
nama
1 baris•4 chars
UTF-8•Spaces: 4
doang.

Contoh yang lebih kelihatan manfaatnya:

index.tstypescript
1
function tampilkanNama<T extends { nama: string }>(item: T): T {
2
console.log(item.nama);
3
return item;
4
}
5
 
6
const hasil = tampilkanNama({ nama: "Andi", umur: 25 });
7
console.log(hasil.umur); // aman, TypeScript tetep inget properti umur
7 baris•236 chars
UTF-8•Spaces: 4

Kalau kamu gak pake generic dan langsung nulis parameter sebagai

PLAINTEXT Snippetplaintext
1
{ nama: string }
1 baris•16 chars
UTF-8•Spaces: 4
, return value-nya bakal ke-cut jadi cuma
PLAINTEXT Snippetplaintext
1
{ nama: string }
1 baris•16 chars
UTF-8•Spaces: 4
aja, dan
PLAINTEXT Snippetplaintext
1
hasil.umur
1 baris•10 chars
UTF-8•Spaces: 4
bakal error karena TypeScript udah "lupa" ada properti
PLAINTEXT Snippetplaintext
1
umur
1 baris•4 chars
UTF-8•Spaces: 4
di situ.

Default Type Parameter, Biar Generic Lebih Fleksibel

Kamu juga bisa kasih nilai default buat type parameter, mirip kayak default parameter di fungsi biasa.

index.tstypescript
1
interface Paginated<T = unknown> {
2
items: T[];
3
page: number;
4
totalPages: number;
5
}
6
 
7
const hasilDefault: Paginated = {
8
items: [],
9
page: 1,
10
totalPages: 1,
11
};
12
 
13
const hasilProduk: Paginated<{ nama: string; harga: number }> = {
14
items: [{ nama: "Baju", harga: 100000 }],
15
page: 1,
16
totalPages: 5,
17
};
17 baris•309 chars
UTF-8•Spaces: 4

Kalau kamu gak nulis type argument sama sekali kayak di

PLAINTEXT Snippetplaintext
1
hasilDefault
1 baris•12 chars
UTF-8•Spaces: 4
, TypeScript bakal pake
PLAINTEXT Snippetplaintext
1
unknown
1 baris•7 chars
UTF-8•Spaces: 4
sebagai default. Tapi kalau kamu butuh tipe yang lebih spesifik, tinggal kasih di dalam
PLAINTEXT Snippetplaintext
1
<>
1 baris•2 chars
UTF-8•Spaces: 4
kayak di
PLAINTEXT Snippetplaintext
1
hasilProduk
1 baris•11 chars
UTF-8•Spaces: 4
. Ini berguna banget buat bikin generic yang tetep bisa dipake dengan gampang meski kamu belum tau mau isi apa dari awal.

Contoh Dunia Nyata, Generic Buat Fungsi Fetch API

Biar makin kebayang, coba lihat contoh generic buat fungsi fetch data dari API, kasus yang hampir pasti ketemu di proyek nyata.

index.tstypescript
1
async function fetchData<T>(url: string): Promise<T> {
2
const response = await fetch(url);
3
return response.json();
4
}
5
 
6
interface User {
7
id: number;
8
nama: string;
9
}
10
 
11
interface Produk {
12
id: number;
13
harga: number;
14
}
15
 
16
const user = await fetchData<User>("/api/user/1");
17
const produk = await fetchData<Produk>("/api/produk/1");
18
 
19
console.log(user.nama); // aman, TypeScript tau ini User
20
console.log(produk.harga); // aman, TypeScript tau ini Produk
20 baris•451 chars
UTF-8•Spaces: 4

Satu fungsi

PLAINTEXT Snippetplaintext
1
fetchData
1 baris•9 chars
UTF-8•Spaces: 4
bisa dipake buat ambil data apa aja, tinggal kasih tau tipe yang diharapkan lewat
PLAINTEXT Snippetplaintext
1
<User>
1 baris•6 chars
UTF-8•Spaces: 4
atau
PLAINTEXT Snippetplaintext
1
<Produk>
1 baris•8 chars
UTF-8•Spaces: 4
. Ini jauh lebih rapi dibanding bikin fungsi terpisah kayak
PLAINTEXT Snippetplaintext
1
fetchUser
1 baris•9 chars
UTF-8•Spaces: 4
dan
PLAINTEXT Snippetplaintext
1
fetchProduk
1 baris•11 chars
UTF-8•Spaces: 4
yang isinya sebenarnya sama persis, cuma beda tipe return-nya.

Kesimpulan

Generics itu sebenarnya bukan soal bikin kode kelihatan lebih canggih atau lebih rumit. Generics itu soal bikin kode reusable tanpa kehilangan informasi tipe, yang berarti kamu tetep dapet semua manfaat type safety meskipun fungsi atau struktur data yang kamu bikin dipake buat banyak tipe data yang berbeda-beda. Mulai dari generic function sederhana, generic interface,

PLAINTEXT Snippetplaintext
1
extends
1 baris•7 chars
UTF-8•Spaces: 4
buat ngasih batasan, sampai default type parameter, semuanya sebenarnya cuma alat buat satu tujuan, bikin kode kamu fleksibel tapi tetep aman.

Di artikel selanjutnya, yang juga jadi penutup Series 1, kita bakal bahas utility types yang paling sering dipake di proyek nyata, kayak

PLAINTEXT Snippetplaintext
1
Partial
1 baris•7 chars
UTF-8•Spaces: 4
,
PLAINTEXT Snippetplaintext
1
Pick
1 baris•4 chars
UTF-8•Spaces: 4
,
PLAINTEXT Snippetplaintext
1
Omit
1 baris•4 chars
UTF-8•Spaces: 4
, dan
PLAINTEXT Snippetplaintext
1
Record
1 baris•6 chars
UTF-8•Spaces: 4
, sebelum kita lanjut ke Series 2 yang bahas type level programming lebih dalam.

Topik Terkait:JavascriptTypscript
Timur Dian Radha Sejati

Timur Dian Radha Sejati

Penulis & Lead Engineer

Software engineer dan konsultan sistem di SejatiDimedia. Berfokus pada perancangan arsitektur berkinerja tinggi, refactoring backend skala enterprise, hingga pengembangan aplikasi mobile, web modern dan integrasi AI.

Punya Masalah Arsitektur atau Ingin Membangun Sistem yang Benar?

Kami siap membantu mengaudit kode, me-refactor arsitektur yang lemot, atau membangun aplikasi bisnis Anda dengan standar enterprise sejak awal.

EKSPLORASI LANJUTAN

Artikel Rekayasa Terkait

Lihat Semua
Utility Types yang Wajib Diketahui Setiap Developer TypeScript
Best Practices
5 menit baca

Utility Types yang Wajib Diketahui Setiap Developer TypeScript

Artikel penutup Series 1 ini ngebahas kumpulan utility types bawaan TypeScript yang paling sering dipake di proyek nyata, kayak `Partial`, `Required`, `Pick`, `Omit`, `Record`, dan `ReturnType`. Setiap utility type dijelasin lewat use case konkret, misalnya `Partial` buat bikin form update yang field-nya opsional, `Omit` buat bikin DTO dari model database, dan `Record` buat bikin mapping objek yang type safe. Di akhir artikel ada juga pengenalan singkat cara bikin utility type sendiri, sebagai jembatan menuju Series 2 yang bahas type level programming lebih dalam.

Baca Artikel
Union, Intersection, dan Discriminated Union, Cara Memodelkan Data yang Punya Banyak Kemungkinan Bentuk
Best Practices
5 menit baca

Union, Intersection, dan Discriminated Union, Cara Memodelkan Data yang Punya Banyak Kemungkinan Bentuk

Artikel ini fokus ke salah satu fitur paling kuat di TypeScript buat memodelkan data yang bentuknya bisa berubah-ubah. Dimulai dari union type dasar buat bilang "nilai ini bisa A atau B", lanjut ke intersection type buat gabungin beberapa tipe jadi satu, sampai discriminated union yang super berguna buat memodelkan state kayak loading, success, dan error di aplikasi nyata. Ada juga pembahasan gimana TypeScript otomatis ngelakuin narrowing berdasarkan properti pembeda, jadi kode kamu lebih aman tanpa perlu banyak pengecekan manual yang rawan salah.

Baca Artikel
Type Inference, Kapan TypeScript Udah Cukup Pinter dan Kapan Perlu Dibantu
Best Practices
5 menit baca

Type Inference, Kapan TypeScript Udah Cukup Pinter dan Kapan Perlu Dibantu

Banyak pemula yang kebiasaan nulis anotasi tipe di mana-mana padahal TypeScript sebenarnya udah bisa nebak sendiri. Artikel ini ngebahas gimana mekanisme type inference kerja di balik layar, kapan inference bekerja dengan baik, dan kapan justru bisa menyesatkan, misalnya di array kosong atau objek yang bentuknya berubah seiring waktu. Ada juga pembahasan soal contextual typing dan kapan sebaiknya tetap nulis anotasi eksplisit meski secara teknis gak wajib. Cocok buat kamu yang ngerasa kode TypeScript-nya kepanjangan gara-gara kebanyakan nulis tipe manual.

Baca Artikel