Module 28: Interfaces and Type Design
Designing types for a real app
🏠The everyday picture
An architect's drawing prevents a door opening into a wall before anything is built. Well-designed types prevent impossible situations before any code runs.
📘What it really is
- Make impossible states impossible to write. Compare:
// Weak: nothing stops paid: false with a paidAt date
interface Order { paid: boolean; paidAt?: string; refunded: boolean; }
// Strong: each state carries exactly what it needs
type OrderState =
| { status: "pending" }
| { status: "paid"; paidAt: string; payment: Payment }
| { status: "refunded"; paidAt: string; refundedAt: string };
- Habits:
- one type per real thing in your app, matching your data model;
- ids refer to other things (
customerId: string), as in Module 21; - units in names or comments (
pricePaise,// in paise); - literal unions instead of loose
stringwherever there's a fixed list; -
required by default; optional only when something can truly be missing.
-
Types first, code second. Writing the types is planning: it's where you discover questions like "can an order be refunded twice?". Then ask AI to write functions for these types, and the checker will hold it to them.
-
Keep all shared types in one file (
types.ts) so everyone, and every AI session, uses the same definitions.
🖱️Try it
In types.ts, design the types for your inventory and expense manager: Product, Supplier, StockMovement
(a discriminated union of "received", "sold" and "adjusted", each with what it needs), and Expense
(with a literal union of categories). Write one line of comment above each explaining a rule it enforces.
✅Quick check
An order has paid: boolean and paidAt?: string. What impossible state can it represent?
Show the answer
paid: false with a paidAt date, or paid: true with no date. A discriminated union prevents both.
Why write types before asking AI to write the functions?
Show the answer
They define exactly what data exists and what's allowed; the AI's code is then checked against them.
