023 — Learn TypeScript Full Course (pizza app, squigglies to generics)
Full companion narrative for Learn TypeScript Full Course — Bob Ziroll's console pizza restaurant from red squigglies through utility types and generics
Video: 023 · Learn TypeScript Full Course · watch · ~2:06:12 · freeCodeCamp / Scrimba full course, instructor Bob Ziroll
Source: this is a written companion to the video. Part of the Favorite TypeScript Videos curated path, modules 00–05 cross-reference (see the coverage map below). One console project throughout (no HTML): a pizza restaurant with a
menuarray, acashRegisternumber, anorderQueuearray, and three functions. Scrimba runs TypeScript in-browser; locally the same step is renamingindex.jstoindex.tswith TypeScript as a dependency.
names the transcript mangles (read this first)
The auto-transcript mishears course terms; this narrative uses the corrected forms: instructor Bob Ziroll (not “Bob’s role”), cashRegister (not “cash in the register”), orderQueue (not “order Q”), addNewPizza (not “ad new pizza”), “loosey-goosey typing”, and the Kent C. Dodds quote (“TypeScript will not make your life terrible, it will show you how terrible your life already is”).
the one idea: types catch TypeErrors before production
TypeScript is a superset of JavaScript: all valid JavaScript is valid TypeScript. It adds compile-time (and in modern IDEs, real-time as-you-type) checking that catches TypeErrors — the app-crushing runtime errors that otherwise surface only after deploy. Three reasons to learn it: confidence (fewer crashes reach users), productivity (typed autocomplete, refactoring, inline errors in VS Code), employability (table stakes at many companies). It does not fix logical errors — only type-shape errors. Analogies: a house blueprint and a stud guard (steel plate stopping a too-long screw from hitting lines) — small upfront work preventing catastrophe.


the project: console pizza restaurant in plain JavaScript
const menu = [
{ name: "Margherita", price: 8 },
// ... 4 pizzas total
];
const cashRegister = 100; // BUG (seeded): must be let, reassigned on orders
const orderQueue = [];
let nextOrderId = 1; // BUG (seeded): declared const in the lesson, must be let
Challenges: (1) addNewPizza(pizzaObj) pushes a menu-shaped object; (2) placeOrder(pizzaName) finds the pizza with menu.find(), adds its price to the register, creates { id, pizza, status: "ordered" }, pushes to the queue, returns it; (3) completeOrder(orderId) finds the order by id, sets status to "completed", returns it. The id uses the nextOrderId++ post-increment trick. Seeded bugs (~00:13:28): const on reassigned variables, and calling completeOrder with a string id while ids are numbers.
red squigglies are found bugs + happy path vs sad path
Copying the JS into index.ts produces red squigglies with zero TypeScript-specific code written. Rule: hover every squiggly (Scrimba does not record the IntelliSense popup). Three free fixes: cashRegister/nextOrderId (const to let), and selectedPizza (“object is possibly undefined” — menu.find() may match nothing). The fix introduces the course’s central habit — happy path vs sad path:
const selectedPizza = menu.find((pizzaObj) => pizzaObj.name === pizzaName);
if (!selectedPizza) {
console.error(`${pizzaName} does not exist in the menu`);
return; // must stop execution so TS narrows the rest
}
cashRegister += selectedPizza.price;
Limit (~00:24:00): the string-vs-number completeOrder bug and the possibly-undefined order are NOT flagged yet — TS cannot help further until explicit types are defined.
basic types and inference
let myName: string = "Bob";
myName = 5; // ERROR: type 'number' is not assignable to type 'string'
Explicit : string here is busy work — TS derives the type from the assigned value. Reassigning a variable to a different data type is a code smell. Style note: in object types separators are free choice (commas, semicolons, nothing) — stay consistent per project.
custom object types, nested types, cost/price catch
type Person = {
name: string;
age: number;
isStudent: boolean;
};
Applied to the app: Pizza type { name: string; price: number }, then addNewPizza(pizzaObj: Pizza) surfaces the seeded bug — test calls pass cost instead of price (“object literal may only specify known properties”). Nested objects two ways: inline, or extracted (Address) when reused. Removing any required property errors — the “rigidity” new users feel is the point in production.

optional properties
type Person = {
name: string;
age: number;
isStudent: boolean;
address?: Address; // ? = may be omitted
};
One ? after the property name. Warning repeated through the course: every flexibility feature (optional, any, loose generics) trades away type safety — ask “do I really need this flexible?” first.
typed arrays and function signatures
const menu: Pizza[] = [
/* ... */
];
const orderQueue: Order[] = [];
function addNewPizza(pizzaObj: Pizza): void {
/* ... */
}
function placeOrder(pizzaName: string): Order | undefined {
/* ... */
}
function completeOrder(orderId: number): Order | undefined {
/* ... */
}
This finally catches the seeded completeOrder("1") string-id call. Same pattern standalone with fetchUserDetails(username: string): User (~01:15:00): explicit return type : User stops a future refactor from silently returning user.username (a string). void for functions returning nothing; Order[] explains the earlier mystery — .find() on orderQueue can also return undefined.
literal types plus unions
type UserRole = "guest" | "member" | "admin";
type User = {
username: string;
role: UserRole;
};
Applied to orders (~01:02:00 challenge): status: "ordered" | "completed" instead of string. Inference subtlety: the newOrder object literal is inferred as { status: string }, which no longer satisfies the narrower Order type — annotate or construct it as an Order so the literal is checked, not widened.
type narrowing
Proving which union member you hold before operating on it: truthiness/falsy guards (if (!selectedPizza) return), === undefined checks, typeof guards. Takeaway quote from the recap (~02:04:54): narrowing code “is a great example of how TypeScript really helps you learn to think more like a senior developer” — edge-case thinking at write time.
any (avoid), unknown, explicit return types
let value: any = 1;
value = "hello"; // no error — checking is OFF
value.nonExistentMethod(); // no error — any silences everything
any turns off checking: do not use it. One legitimate use is temporary scaffolding during JS-to-TS migration, with a warning that temporary anys are hard to remove later (better to leave warnings visible and relax strictness config instead).
utility types: Partial and Omit
type UpdatedUser = Partial<User>; // every property optional — update payloads
function updateUser(id: number, updates: UpdatedUser) {
/* ... */
}
function addNewUser(newUser: Omit<User, "id">): User {
const user: User = { id: nextUserId++, ...newUser };
users.push(user);
return user;
}
Partial fails for creation; Omit with a second type-argument (property name/names to remove) fits. Pizza-app mirror: addNewPizza(pizzaObj: Omit<Pizza, "id">) builds newPizza: Pizza with id: nextPizzaId++. Scrimba caveat (~01:47:30): the in-browser editor did not understand Omit at recording time, so the instructor demonstrates via VS Code screenshots. Docs pointer: TypeScript handbook utility-types page (Partial and Omit sections).


generics and the widening gotcha
function getLastItem<PlaceholderType>(array: PlaceholderType[]): PlaceholderType {
return array[array.length - 1];
}
getLastItem([1, 2, 3]); // inferred PlaceholderType = number
Convention is <T>; the instructor starts with <Type>/<PlaceholderType>, then switches. Applied as generic addToArray<T>(array: T[], item: T): T[]. Payoff lesson (~01:59:00): addToArray(orderQueue, {..., status: "done"}) does NOT error — TS infers T as the widened object literal (status: string). Fix with an explicit type argument:
addToArray<Order>(orderQueue, { ... , status: "done" });
// ERROR: '"done"' is not assignable to '"ordered" | "completed"'
Mini-challenge answer: the blank in addToArray<___>(orderQueue, ...) is Order.
recap and scope
Closing recap (~02:04:21): basic + literal + custom types → optional properties → unions → narrowing → built-in utility types → generics. Stated scope: not exhaustive; planned follow-ups include TypeScript with the DOM. Learner advice: replay with hands on keyboard; use Google/ChatGPT with specific questions when stuck.

where this course lands on the module map
| module | course coverage | narrative section |
|---|---|---|
| 00 start (env, js-to-ts) | js pizza app, index.ts move, squigglies | project, squigglies |
| 01 why (safety net, tsc) | confidence/productivity/employability, blueprint, sad path | one idea, squigglies |
| 02 basics (inference, any/unknown/never) | inference, any, unknown, void | basic types, any |
| 03 shapes (arrays, tuples, interfaces, optional/readonly) | Person/Pizza/Address, optional, Pizza[]/Order[] (types-as-interfaces; tuples/readonly not covered) | custom types, optional, arrays |
| 04 functions (signatures, unions, narrowing, guards) | signatures, return types, unions, narrowing | signatures, literals, narrowing |
| 05 architecture (literals, enums, generics, utility) | literals, UserRole enum-pattern, Partial/Omit, generics | literals, utility types, generics |
| 06 config (tsconfig strict, MCP) | not covered (strict config mentioned only in passing re any) | — |
| 07 capstone (analyst agent) | not covered | — |
Gaps (source from other itinerary videos): tuples, readonly, interfaces-vs-types, never, tsconfig strict, enums as a type.
Prev: 022 — #1 - What is TypeScript || Install Typescript || TypeScript vs JavaScript · Next: 024 — #01 Introduction to TypeScript | TypeScript Basics & Data Types | A Complete TypeScript Course
Transcript notes: “Bob Ziroll” (not “Bob’s role”), cashRegister, orderQueue, addNewPizza, “loosey-goosey typing”, Kent C. Dodds quote. Frames run 00:00:00 (“Why learn TypeScript?” slide) through ~02:04:21 (recap slide).