SEJATIDIMEDIA Logo
Tersedia Sesi Konsultasi Gratis
SejatiDimedia Logo

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.

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
Union, Intersection, dan Discriminated Union, Cara Memodelkan Data yang Punya Banyak Kemungkinan Bentuk

Pernah gak sih kamu bikin fungsi yang parameternya bisa nerima lebih dari satu jenis data? Misalnya fungsi yang bisa nerima

PLAINTEXT Snippetplaintext
1
string
1 baris•6 chars
UTF-8•Spaces: 4
atau
PLAINTEXT Snippetplaintext
1
number
1 baris•6 chars
UTF-8•Spaces: 4
, tapi kamu bingung mau nulis tipenya gimana. Nah, di sinilah union type mulai kepake.

Union Type, Bilang "Ini Bisa A atau B"

Union type ditulis pakai simbol

PLAINTEXT Snippetplaintext
1
|
1 baris•1 chars
UTF-8•Spaces: 4
, dan artinya sebuah nilai bisa jadi salah satu dari beberapa tipe yang disebutin.

index.tstypescript
1
function formatId(id: string | number) {
2
return `ID-${id}`;
3
}
4
 
5
formatId("abc"); // aman
6
formatId(123); // aman
7
formatId(true); // Error, boolean bukan bagian dari union
7 baris•170 chars
UTF-8•Spaces: 4

Yang perlu diinget, begitu sebuah variabel bertipe union, TypeScript cuma ngizinin kamu pakai fitur yang emang ada di semua kemungkinan tipe dalam union itu. Contohnya kayak gini:

index.tstypescript
1
function cetak(value: string | number) {
2
console.log(value.toUpperCase()); // Error, number gak punya toUpperCase
3
}
3 baris•117 chars
UTF-8•Spaces: 4

Biar aman, kamu perlu ngelakuin narrowing dulu, alias mastiin dulu tipe pastinya sebelum make fitur yang spesifik ke satu tipe:

index.tstypescript
1
function cetak(value: string | number) {
2
if (typeof value === "string") {
3
console.log(value.toUpperCase()); // aman, TypeScript udah tau ini string
4
} else {
5
console.log(value.toFixed(2)); // aman, TypeScript udah tau ini number
6
}
7
}
7 baris•245 chars
UTF-8•Spaces: 4

Narrowing ini penting banget dipahami, karena ini yang bikin union type tetep aman dipakai. TypeScript otomatis "mempersempit" kemungkinan tipe berdasarkan pengecekan yang kamu lakuin, kayak

PLAINTEXT Snippetplaintext
1
typeof
1 baris•6 chars
UTF-8•Spaces: 4
,
PLAINTEXT Snippetplaintext
1
instanceof
1 baris•10 chars
UTF-8•Spaces: 4
, atau pengecekan properti.

Intersection Type, Gabungin Beberapa Tipe Jadi Satu

Kalau union itu "atau", intersection itu "dan". Ditulis pakai simbol

PLAINTEXT Snippetplaintext
1
&
1 baris•1 chars
UTF-8•Spaces: 4
, dan artinya sebuah nilai harus punya semua properti dari tipe-tipe yang digabungin.

index.tstypescript
1
interface HasNama {
2
nama: string;
3
}
4
 
5
interface HasUmur {
6
umur: number;
7
}
8
 
9
type Person = HasNama & HasUmur;
10
 
11
const orang: Person = {
12
nama: "Andi",
13
umur: 25,
14
};
14 baris•166 chars
UTF-8•Spaces: 4

PLAINTEXT Snippetplaintext
1
Person
1 baris•6 chars
UTF-8•Spaces: 4
di sini wajib punya
PLAINTEXT Snippetplaintext
1
nama
1 baris•4 chars
UTF-8•Spaces: 4
dan
PLAINTEXT Snippetplaintext
1
umur
1 baris•4 chars
UTF-8•Spaces: 4
sekaligus. Kalau salah satu gak ada, TypeScript bakal error.

Intersection type paling sering kepake buat nggabungin beberapa "potongan" tipe yang masing-masing punya tanggung jawab sendiri, biar kamu gak perlu nulis ulang properti yang sama berkali-kali.

index.tstypescript
1
interface Timestamped {
2
createdAt: Date;
3
updatedAt: Date;
4
}
5
 
6
interface Product {
7
id: string;
8
price: number;
9
}
10
 
11
type ProductWithTimestamp = Product & Timestamped;
11 baris•169 chars
UTF-8•Spaces: 4

Cara ini bikin kode lebih rapi karena

PLAINTEXT Snippetplaintext
1
Timestamped
1 baris•11 chars
UTF-8•Spaces: 4
bisa dipake ulang di banyak entitas lain, bukan cuma
PLAINTEXT Snippetplaintext
1
Product
1 baris•7 chars
UTF-8•Spaces: 4
.

Discriminated Union, Kombo Paling Kuat buat Modelin State

Nah, ini bagian yang paling sering kepake di aplikasi nyata. Discriminated union itu union type yang tiap anggotanya punya satu properti "pembeda" (biasanya disebut discriminant), yang nilainya berupa literal type yang unik buat masing-masing kemungkinan.

Bayangin kamu lagi bikin state buat proses fetch data dari API. Ada tiga kemungkinan, lagi loading, berhasil dapat data, atau gagal dan dapat error.

index.tstypescript
1
type FetchState =
2
| { status: "loading" }
3
| { status: "success"; data: string[] }
4
| { status: "error"; message: string };
4 baris•127 chars
UTF-8•Spaces: 4

Properti

PLAINTEXT Snippetplaintext
1
status
1 baris•6 chars
UTF-8•Spaces: 4
di sini adalah discriminant-nya. Nilainya berupa literal string yang unik di tiap kemungkinan,
PLAINTEXT Snippetplaintext
1
"loading"
1 baris•9 chars
UTF-8•Spaces: 4
,
PLAINTEXT Snippetplaintext
1
"success"
1 baris•9 chars
UTF-8•Spaces: 4
, atau
PLAINTEXT Snippetplaintext
1
"error"
1 baris•7 chars
UTF-8•Spaces: 4
.

Yang bikin ini powerful, TypeScript otomatis ngelakuin narrowing begitu kamu ngecek nilai

PLAINTEXT Snippetplaintext
1
status
1 baris•6 chars
UTF-8•Spaces: 4
:

index.tstypescript
1
function render(state: FetchState) {
2
if (state.status === "loading") {
3
return "Lagi loading...";
4
}
5
 
6
if (state.status === "success") {
7
return `Dapet ${state.data.length} data`; // aman, TypeScript tau ada data di sini
8
}
9
 
10
return `Error: ${state.message}`; // aman, TypeScript tau ada message di sini
11
}
11 baris•317 chars
UTF-8•Spaces: 4

Perhatiin, kamu gak perlu nulis pengecekan tambahan kayak

PLAINTEXT Snippetplaintext
1
if ("data" in state)
1 baris•20 chars
UTF-8•Spaces: 4
buat mastiin properti
PLAINTEXT Snippetplaintext
1
data
1 baris•4 chars
UTF-8•Spaces: 4
ada. TypeScript udah otomatis tau, begitu
PLAINTEXT Snippetplaintext
1
state.status === "success"
1 baris•26 chars
UTF-8•Spaces: 4
, berarti bentuk
PLAINTEXT Snippetplaintext
1
state
1 baris•5 chars
UTF-8•Spaces: 4
di titik itu pasti punya
PLAINTEXT Snippetplaintext
1
data
1 baris•4 chars
UTF-8•Spaces: 4
, karena itu satu-satunya kemungkinan di union yang punya
PLAINTEXT Snippetplaintext
1
status: "success"
1 baris•17 chars
UTF-8•Spaces: 4
.

Bandingin kalau kamu modelin state ini tanpa discriminated union, misalnya cuma pakai satu interface dengan banyak properti optional:

index.tstypescript
1
interface FetchStateBuruk {
2
status: string;
3
data?: string[];
4
message?: string;
5
}
5 baris•86 chars
UTF-8•Spaces: 4

Cara ini jauh lebih rawan bug. Gak ada yang maksa kamu buat mastiin kombinasi properti yang valid. Kamu bisa aja punya state dengan

PLAINTEXT Snippetplaintext
1
status: "success"
1 baris•17 chars
UTF-8•Spaces: 4
tapi
PLAINTEXT Snippetplaintext
1
data
1 baris•4 chars
UTF-8•Spaces: 4
-nya
PLAINTEXT Snippetplaintext
1
undefined
1 baris•9 chars
UTF-8•Spaces: 4
, atau
PLAINTEXT Snippetplaintext
1
status: "error"
1 baris•15 chars
UTF-8•Spaces: 4
tapi malah ada
PLAINTEXT Snippetplaintext
1
data
1 baris•4 chars
UTF-8•Spaces: 4
yang keisi. TypeScript gak bisa nolongin kamu di sini karena secara tipe, semua kombinasi itu "valid".

Exhaustiveness Check, Bonus dari Discriminated Union

Kalau kamu masih inget pembahasan

PLAINTEXT Snippetplaintext
1
never
1 baris•5 chars
UTF-8•Spaces: 4
di Part 2, discriminated union itu pasangan yang pas banget buat exhaustiveness check.

index.tstypescript
1
function render(state: FetchState): string {
2
switch (state.status) {
3
case "loading":
4
return "Lagi loading...";
5
case "success":
6
return `Dapet ${state.data.length} data`;
7
case "error":
8
return `Error: ${state.message}`;
9
default:
10
const _check: never = state;
11
return _check;
12
}
13
}
13 baris•323 chars
UTF-8•Spaces: 4

Kalau suatu hari kamu nambahin kemungkinan baru ke

PLAINTEXT Snippetplaintext
1
FetchState
1 baris•10 chars
UTF-8•Spaces: 4
, misalnya
PLAINTEXT Snippetplaintext
1
{ status: "empty" }
1 baris•19 chars
UTF-8•Spaces: 4
, tapi lupa nambahin case buat itu di fungsi
PLAINTEXT Snippetplaintext
1
render
1 baris•6 chars
UTF-8•Spaces: 4
, TypeScript bakal langsung error di baris
PLAINTEXT Snippetplaintext
1
_check
1 baris•6 chars
UTF-8•Spaces: 4
. Ini ngebantu banget biar kamu gak lupa nangani kasus baru pas aplikasi berkembang.

Kesimpulan

Union, intersection, dan discriminated union itu tiga alat yang saling melengkapi buat memodelkan data di dunia nyata yang jarang banget punya bentuk tunggal dan kaku. Union buat bilang "bisa salah satu dari ini", intersection buat bilang "harus punya semua ini", dan discriminated union buat memodelkan state yang punya banyak kemungkinan tapi tetep type safe berkat properti pembeda. Begitu kamu terbiasa mikir pakai ketiganya, kamu bakal ngerasa banyak masalah pemodelan data yang tadinya ribet, jadi jauh lebih gampang dituliskan dengan aman.

Di artikel selanjutnya, kita bakal masuk ke generics, salah satu fitur yang sering dianggap menakutkan sama pemula, padahal konsepnya sederhana banget kalau dijelasin pake analogi yang pas.

Topik Terkait:JavascriptTypescript
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
Generics, dari Dasar sampai Constraint
Best Practices
5 menit baca

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.

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