Fluent Interfaces with Types
Chain methods while tracking accumulated state in types.
Fluent Interfaces with Types is a free TypeScript Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Tracking What Was Set
A plain builder lets you call build() at any time, even with required fields missing. We can do better by tracking accumulated state in the type system using a generic type parameter.
A State Type Parameter
We give the builder a generic S that records which keys have been provided so far. As each method runs, we widen S to include a new key.
class Builder<S> {
// S is a record of keys set so far, e.g. {} or { url: string }
private data: Record<string, unknown> = {};
}Each Method Adds a Key
A setter returns a new typed builder whose S includes the freshly added key. The runtime stores the value; the type widens accordingly.
set<K extends string, V>(key: K, value: V): Builder<S & Record<K, V>> {
this.data[key] = value;
return this as unknown as Builder<S & Record<K, V>>;
}The Intersection Trick
The expression S & Record<K, V> intersects the previous state with the new key. After two calls the type knows about both keys precisely.
type Empty = {};
type AfterUrl = Empty & Record<"url", string>;
type AfterBoth = AfterUrl & Record<"method", string>;
// AfterBoth = { url: string; method: string }A Concrete Fluent Builder
Here a request builder threads its accumulated shape through each call. The compiler always knows exactly which fields exist.
class Req<S> {
private d: Record<string, unknown> = {};
url(u: string): Req<S & { url: string }> {
this.d.url = u; return this as any;
}
method(m: string): Req<S & { method: string }> {
this.d.method = m; return this as any;
}
}The Type Grows as You Chain
Each call in the chain produces a more specific builder type. Hovering over the result in an editor shows the accumulated state.
const partial = new Req<{}>().url("/x");
// type: Req<{ url: string }>
const full = partial.method("POST");
// type: Req<{ url: string; method: string }>Why Track State at All?
Once the type carries the set of provided keys, we can later constrain build() to only compile when required keys are present. The state parameter is the foundation for that guarantee.
Reading the Accumulated Shape
You can extract the accumulated shape to inspect it. This confirms the builder's type reflects exactly what was configured.
type StateOf<T> = T extends Req<infer S> ? S : never;
type S1 = StateOf<Req<{ url: string }>>; // { url: string }Runtime Stays Simple
All the cleverness lives in the types. At runtime each setter just stores a value in an object. The casts (as any) bridge the runtime object to the richer compile-time type.
const r = new Req<{}>().url("/users").method("GET");
console.log("configured url and method");Generic State as a Ledger
Think of S as a ledger of what has been set. Methods append entries; the build step can later read the ledger to decide whether construction is allowed.
Trade-offs
Type-level state tracking gives precise IDE feedback and prevents incomplete builds, at the cost of a few casts and more advanced generics. It pays off for APIs many people use.
Quick Check
Quick check on this lesson.
Recap
A fluent typed builder carries a generic state parameter S. Each setter intersects S with the new key (S & Record<K, V>) and returns a more specific builder, giving the compiler a precise ledger of configured fields.
Frequently asked questions
Is the “Fluent Interfaces with Types” lesson free?
Yes — the full text of “Fluent Interfaces with Types” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.
What will I learn in “Fluent Interfaces with Types”?
Chain methods while tracking accumulated state in types. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start TypeScript Academy?
No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Fluent Interfaces with Types” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this TypeScript Academy lesson?
Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The Builder Pattern Basics
- Fluent Interfaces with Types
- Enforcing Required Steps
- Immutable Builders