This episode introduces generics, the most powerful tool for writing code that works across many types. You'll learn generic functions, type parameters, constraints with extends, and generic interfaces and classes with default type parameters.

Imagine writing identical functions that handle arrays of strings, arrays of numbers, and arrays of objects. Without help, you either copy the code three times or throw away type guarantees with any. Generics solve both at once: one implementation, types still preserved.
Generics are a way to write parametric types, that is, types that accept type arguments. The analogy is with functions over values: a value function takes a value argument and returns a value, while a generic takes a type argument and produces a concrete type.
Episode 8 covers generic functions, constraints so type parameters don't run wild, generics on interfaces and classes, and default type parameters. These are the foundations for reading and writing real TypeScript libraries.
A generic function is declared with a type parameter inside angle brackets:
function elemenPertama<T>(data: T[]): T | undefined {
return data[0];
}
const angka = elemenPertama([10, 20, 30]);
const kata = elemenPertama(["a", "b"]);The declaration function elemenPertama<T>(data: T[]): T states that the return type equals the element type of the input array. Called with a number array, the result is number. TypeScript infers T automatically, so you don't need to write the type on the calling side.
The most classic example is the identity function:
function identitas<T>(nilai: T): T {
return nilai;
}
const teks = identitas("halo");
const nomor = identitas(42);The identitas function accepts any value and returns it with the same type. Without generics, this function would have to be any and lose all type information. With generics, every call gets the precise type.
An unconstrained type parameter accepts anything. A constraint limits it to a specific shape:
interface PunyaPanjang {
length: number;
}
function ukuran<T extends PunyaPanjang>(input: T): number {
return input.length;
}
ukuran("halo");
ukuran([1, 2, 3]);
ukuran(123);The constraint T extends PunyaPanjang guarantees the input has a length property, so the access input.length is safe. A call with a number is rejected by the compiler because number has no length. Constraints keep the function body valid for every type it allows.
Generics aren't just for functions. Interfaces and classes can also be parametric:
interface Kotak<T> {
isi: T;
}
class Penyimpan<T> {
private nilai: T;
constructor(nilai: T) {
this.nilai = nilai;
}
ambil(): T {
return this.nilai;
}
}
const kunci = new Penyimpan<string>("rahasia");
const kataKunci: string = kunci.ambil();interface Kotak<T> defines a contract whose content has type T. class Penyimpan<T> holds a value of type T and returns it as type T. An instance of Penyimpan<string> can only store and return strings, fully checked by the compiler.
A type parameter can have a default value like a function parameter:
interface Respons<T = string> {
sukses: boolean;
data: T;
}
const teksRespons: Respons = { sukses: true, data: "ok" };
const angkaRespons: Respons<number> = { sukses: true, data: 200 };interface Respons<T = string> uses string when the caller doesn't mention the type. Calls with Respons<number> remain possible. Defaults make generic types easy to use for the common case without sacrificing flexibility for special cases.
Info
Type parameter naming convention: use a single uppercase letter like T, K, or V for simple cases, and descriptive names like Data or Error for generics whose meaning matters. Clear names help readers understand the relationships between types.
Episode 8 gives you the most powerful weapon of the TypeScript type system: generics. With functions, constraints, interfaces, classes, and default type parameters, you can write one implementation that's safe for many types without copying code or falling back to any.
Key takeaways:
extends constraint limits a type parameter to a specific shape.In the next episode 9 we'll discuss classes, inheritance, modifiers, and abstraction — how to build objects with shared behavior and control member access under the type system.