This episode unlocks TypeScript's expressive power: union types with the | operator for values that can take several shapes, literal types to lock in specific values, and intersection types with the & operator to combine object types.

The primitive types from episode 3 are the raw materials. Episode 4 teaches you how to combine them to reflect real data — because real-world data rarely has a single type. An ID can be a number or a string, an order status can be one of five possibilities, and an object can be a combination of several shapes.
Three concepts you'll master: union types for values that can take several forms, literal types to lock in specific values, and intersection types to combine object shapes. Together they're the foundation for building accurate domain models.
A union type is written with the pipe operator |:
type Id = string | number;
let userId: Id = 123;
userId = "a1b2c3"; // valid, masih bertipe Id
function formatId(id: Id): string {
return String(id);
}The declaration type Id = string | number; means an Id value can hold a string or a number. This union is often used for IDs coming from different sources, for example from a database or from URL parameters.
A problem arises when the union value is used: TypeScript doesn't allow operations that only apply to one of its members. For example, toUpperCase can't be called directly on string | number — the compiler doesn't know which shape you're holding.
function sambut(id: Id): string {
if (typeof id === "string") {
return id.toUpperCase();
}
return `Nomor: ${id}`;
}The typeof id === "string" check above narrows the type inside the if block. This is called type narrowing, and we'll cover it thoroughly in episode 11.
Besides restricting the kind of a value, TypeScript can restrict specific values:
type Metode = "GET" | "POST" | "PUT" | "DELETE";
type Tahap = "idle" | "loading" | "sukses" | "gagal";
let tahap: Tahap = "loading";
tahap = "sukses"; // valid
tahap = "batal"; // error: "batal" tidak ada di unionThe Tahap type above only allows four specific strings. The payoff is huge: misspelling a status becomes an immediate error, not a hard-to-track runtime bug.
Combining literals with unions is the most recommended alternative to enum (details in episode 6):
type StatusOrder = "baru" | "diproses" | "dikirim" | "selesai" | "dibatalkan";
function cekStatus(status: StatusOrder): string {
return `Order sedang dalam tahap: ${status}`;
}
console.log(cekStatus("dikirim"));An intersection type is written with the & operator and requires the value to satisfy all shapes at once:
type Identitas = {
nama: string;
};
type Karyawan = Identitas & {
jabatan: string;
gaji: number;
};
const budi: Karyawan = {
nama: "Budi",
jabatan: "Engineer",
gaji: 15_000_000,
};The variable budi: Karyawan must have all the fields from Identitas plus the extra fields from the inline declaration. Intersections are most useful for combining types from several sources, such as user data from two different APIs.
The easiest distinction to remember:
A | B is shaped like A or B.A & B is shaped like A and B simultaneously.Warning
Don't confuse this with the meaning of "intersection" in set theory. On objects, A & B merges properties — the value must satisfy both. For value sets like literals, the meaning is indeed an intersection. Focus on the object case, which is what you'll use most often.
In practice, unions are used far more often than intersections. Literal unions for status, unions for nullable values, and discriminated unions in episode 11 will become your main tools. Intersections are rarer, but irreplaceable when combining object types from different sources.
Episode 4 gave you the two most important operators for designing types: | for choices and & for combining, plus literal types for locking in values. You can now model IDs, statuses, and composite objects accurately.
Key takeaways:
string | number means the value can be either shape."GET" | "POST".A & B merges the properties of both types.typeof is required before using a union value.In the next episode 5 we'll discuss interface, type alias, and type declarations — the main way to name and define recurring object shapes, plus when to choose interface and when to choose type.