Chapter 01 Testing How engineers prove the code works without shipping it and hoping.
Unit test
A test that checks one small piece of code — usually a single function — in isolation. No database, no network, no file system. Just: given this input, did the function return the right output?
Testing a single ingredient before you put it in the recipe. You taste the salt alone to make sure it's salt and not sugar. You don't need the whole soup to verify that.
A function calculateXP(lessonsCompleted, difficulty) in Drivia. You pass it (5, 'hard') and assert the return is 250. That's a unit test.
For pure logic: calculations, transformations, validators. Cheap, fast, and the first thing you write when a bug slips through.
If your unit test needs the database or a network call, it isn't a unit test anymore — that's an integration test, and it's slower and flakier. Keep unit tests pure.
Integration test
A test that verifies two or more pieces work correctly together. Typically your code plus a real dependency: a database, an API, a queue.
Tasting the whole soup after you've added the salt, the broth, and the vegetables. You're not testing each ingredient — you're testing whether they play nice in the same pot.
Calling your Supabase insert function against a real test database and verifying the row actually lands with the right RLS policies applied.
When the risk isn't in any single function but in the boundary between them — your code meeting Postgres, your API meeting Stripe.
End-to-end test (E2E)
A test that drives the entire system the way a real user would — open the browser, click the button, see the result.
Ordering a pizza online to make sure the restaurant works. You don't care about the oven or the driver in isolation; you care that a hungry person gets a pizza.
A Playwright test that logs into Drivia, starts a lesson, answers a quiz, and asserts the XP counter went up by the expected amount.
For critical user journeys you can't afford to break — signup, payment, the JAX tutor. One E2E test is worth a hundred unit tests for catching integration surprises.
E2E tests are slow and flaky. If you have more than a few dozen, you'll hate them. Write them for the journeys that would cost you money if they broke.
TDD (Test-Driven Development)
Write the failing test first, then write the code that makes it pass. Sounds backwards. Works surprisingly well. Four words that all mean ‘a fake version of something so the real thing doesn't get used in a test.’ The distinctions matter to pedants:
A carpenter marking the wood before cutting it. The test is your pencil line — it defines exactly where the code should land.
You want a function that formats currency. You write: expect(format(1234.5)).toBe('$1,234.50'). It fails because the function doesn't exist yet. Then you write the function until the test passes. A stub returns hard-coded answers ('always return user #1'). A mock is a stub that also records how it was called, so you can assert sendEmail was called once with these arguments. A spy wraps the real thing and just records calls. A fake is a working lightweight replacement — an in-memory database instead of Postgres.
When you're genuinely uncertain about the design. Writing the test first forces you to think about how the code will be used before you write it. Mock / Stub / Spy / Fake Whenever the real dependency is slow, flaky, expensive, or has side effects (sends email, charges a credit card). Never mock something you own — just use the real thing.
Over-mocking makes tests that pass with broken production code. If the test and the thing it's testing are both fake, you're testing fiction.
Fixture
Pre-built test data that your tests start from. 'A user with 3 completed lessons' is a fixture.
The set dressing on a movie stage. You don't rebuild the kitchen every time you shoot the kitchen scene — you keep it ready.
A seedTestUser() helper that creates a learner row, a subscription, and five lessons in one call, so every test can start from that baseline.
Snapshot test
A test that saves the output of a function the first time it runs, and then fails if the output ever changes.
Taking a photograph of your living room and checking it every week. If something moved, you want to know — even if you don't know what should be there.
Rendering a React component and saving the HTML. The next run compares the new HTML to the saved snapshot. If anyone changed the component accidentally, the test screams.
Easy to abuse. People update the snapshot without reading it and the test becomes a rubber stamp. Review snapshot changes like you'd review code.
Test coverage
The percentage of your code that gets executed when the test suite runs.
How much of the field the spotlight lights up. 100% coverage doesn't mean the code works — it means no line was untouched. You can still shoot yourself with a fully lit spotlight.
As a floor, not a goal. Aim for 60-80% on business logic, 0% is fine on glue code. Chasing 100% produces garbage tests that exist to hit the number.
Coverage measures execution, not verification. A test that runs a line but asserts nothing still counts toward coverage. Don't cargo-cult the number.
coverage = (lines run by tests / total lines) × 100
Flaky test
A test that sometimes passes and sometimes fails on the same code.
A car that starts most mornings but not every morning. You can't trust it to take you to work.
A test that relies on setTimeout(500) and fails on a slow CI runner. Or a test that depends on the order other tests ran in.
Never. Flaky tests are worse than no tests because they teach the team to ignore red builds. Fix them or delete them.
Root causes are almost always time (race conditions), shared state between tests, or real network calls. Find the cause; don't add retries.
Regression
A bug that re-appears after it was fixed. The code 'regressed.'
You fixed the XP calculator last sprint. This sprint, someone refactored the scoring service and the bug came back. That's a regression.
When you fix a bug, write a test for it the same day. That test becomes a regression test — its whole job is to scream if the bug ever returns.
Chapter 02 Refactoring & Code Smells Changing the shape of code without changing what it does.
Refactoring
Changing the internal structure of code without changing its behavior. Same inputs, same outputs, different insides.
Reorganizing your garage. The same tools, boxes, and bikes are in there — but now you can actually find them. The car still parks the same way.
Extracting a 200-line handleCheckout function into five smaller functions, one per step (validate, charge, fulfill, email, log). The user still checks out identically, but the code is readable.
Before you add a feature to messy code. Cleaning up first makes the new feature take half the time. The ‘two hats’ rule: refactor with one hat, add features with the other — never both at once.
Refactoring without tests is renovation without blueprints. Write tests first so you can prove behavior didn't change.
Code smell
A surface-level symptom that hints at a deeper problem. The code ‘smells off’ — not necessarily wrong, but suspicious.
A weird noise from your car engine. Might be nothing. Might be about to leave you stranded on I-35. A mechanic knows which noises matter.
A function with 400 lines, a file with 15 nested if statements, a class with 30 methods. None of these are illegal — they just reek.
DRY (Don't Repeat Yourself)
Every piece of knowledge should live in exactly one place. If you have the same business rule in three files, any change has to hunt all three down.
One source of truth for your wedding guest list. If you let your mom, your sister, and your venue each keep their own copy, they will disagree the day of.
Stripe pricing logic copy-pasted into checkout, invoices, and the admin dashboard. Extract it to lib/pricing.ts and import it everywhere.
Juniors over-apply DRY. Two pieces of code that look the same but exist for different reasons should stay separate — deduping them creates coupling that hurts later. Rule of thumb: dedupe on the third occurrence.
KISS (Keep It Simple, Stupid)
When in doubt, pick the boring solution. Simple code is cheaper to read, debug, and change. Cleverness is a debt you pay back with interest. Don't build something because you might need it someday. Build it when you actually need it.
A for loop is often better than a recursive reduce with three lambdas. YAGNI (You Aren't Gonna Need It) Adding multi-currency support to Drivia now because ‘we might expand to Europe’ is YAGNI. Ship USD. When Europe shows up, add it then — and you'll build it better with real requirements.
Every time a developer says 'let's make this flexible for the future.' The future almost never looks like they imagined.
SOLID
Five design principles for object-oriented code, named by the acronym: Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion.
Building codes for a house. None of them are laws of physics — they're hard-won rules that keep houses from collapsing.
Single responsibility: a class that sends email should only send email, not also calculate taxes. Dependency inversion: your checkout code should depend on an EmailSender interface, not directly on Resend, so you can swap providers.
SOLID is best as a lens, not a checklist. If your code is hard to test or hard to change, one of the SOLID principles is being violated. Ask which one.
Technical debt
Shortcuts you took in code that you'll have to pay for later — with interest. Sometimes the shortcut is worth it; sometimes you should never have taken it.
Borrowing money. Sometimes a loan helps you buy a house you couldn't otherwise. Sometimes it's a payday loan that bleeds you for years. Tech debt is the same.
You copy-pasted the Stripe webhook handler three times to ship a deadline. It worked. Now every Stripe change means editing three places and forgetting one. That's the interest on the debt.
Deliberate tech debt to hit a deadline is fine — if you write down what you did and schedule the payoff. Undocumented tech debt is just a future outage with your name on it.
Spaghetti code
Code where everything is tangled with everything else, so pulling on one strand moves ten others.
A React component that reads from three contexts, writes to a global Zustand store, side-effects into localStorage, and calls an API — all in one useEffect. Touch it and something you don't expect breaks.
God object / God class
A class or file that knows too much and does too much. It becomes the center of the universe, and every change passes through it.
A UserManager class with 5,000 lines that handles auth, profiles, billing, email, notifications, and the kitchen sink. Break it up.
Chapter 03 Change Management & Blast Radius How engineers reason about the cost of getting it wrong.
Blast radius
How much damage a bad deploy, a bad query, or a bad decision can do. 'Small blast radius' = you only break yourself. 'Large blast radius' = you break every paying customer.
A firework in your backyard vs. a firework in a gas station. Same spark, very different consequences.
Running a migration on one tenant's database = small blast radius. Running it on the shared production Postgres during business hours = large blast radius.
Every time before you push, run, delete, or force-push. Ask: if this is wrong, who pays? If the answer is 'everyone,' slow down and get a second set of eyes.
The most dangerous blast radius is the one you didn't see coming. Shared databases, shared caches, shared queues — they look local and turn out to be global.
Feature flag
A switch in code that lets you turn a feature on or off without redeploying. New code ships dark, then you flip it on for 1% of users, then 10%, then everyone.
A dimmer switch instead of a light switch. You can turn on a feature just a little to see how it goes, then crank it up when you're confident.
Wrap the new JAX tutor in a flag jax_v2_enabled. Ship the code. Enable it for your own account first. Then internal admins. Then 1% of learners. At every step you can kill it in 10 seconds without a deploy.
Anytime a change could break things. Feature flags convert a scary deploy into a boring one and a rollback into a config change.
Canary deploy
Rolling out a new version to a tiny slice of traffic first. If the canary (small sample of users) is fine, you push to more. If it dies, you roll back.
Miners used to send a canary into the mineshaft. If the bird survived, the air was safe. Same idea with less bird cruelty.
Vercel routes 5% of traffic to the new Drivia build for 15 minutes. If error rates don't spike, the rest of traffic flips over.
Blue-green deploy
Run two full copies of production (blue and green). Blue is live; green is the new version. Once green is ready, flip the router — one second, everyone's on green. If green breaks, flip back.
Two identical stages at a concert. The band sets up on stage B while the old band plays on stage A. The lights flip over instantly and nobody missed a beat.
Rollback
Reverting a bad deploy to the previous version.
Fast, always fast. The moment you suspect a deploy is bad, roll back first, then investigate. Don't debug in prod while customers are angry.
Hotfix
An emergency patch that skips the normal release train because something is actively broken in production.
Hotfixes are how bugs get worse. A hotfix with no tests and no review is how you cause the second outage while fixing the first. Keep hotfixes tiny and paired with at least one other engineer.
Idempotent
An operation you can run many times and the result is the same as running it once. Safe to retry.
Pressing the elevator button. Hitting it ten times is the same as hitting it once — the elevator still comes. Compare that to pushing the gas pedal, which is not idempotent.
‘Set user's plan to pro’ is idempotent. ‘Charge the user $29’ is NOT — retry it and you double-charge them. Use idempotency keys on payment operations so retries are safe.
Anytime a request might be retried: webhooks, background jobs, anything over a flaky network. Design operations to be idempotent by default.
f(f(x)) = f(x)
Graceful degradation
When a part of the system fails, the rest keeps working — just worse.
If the AI provider is down, Drivia shows the last cached response and a ‘JAX is taking a nap’ banner instead of blowing up the whole lesson page.
Chapter 04 Architecture & System Design How the pieces fit together at the highest level.
Monolith
One big codebase that does everything. Frontend, backend, database, auth, billing — all in one repo, deployed as one unit.
A single big-box store. Everything under one roof, one manager, one opening hour. Efficient and simple until the store gets too big to run.
Early Drivia was a monolith and that was correct. One Next.js app handles everything. You can ship features fast because there's no cross-service coordination.
Almost always, until you have at least 20 engineers or a clear scaling bottleneck. Most ‘microservices’ startups would've shipped faster as monoliths.
‘Monolith’ is often used as an insult, which is junior thinking. A clean monolith beats messy microservices every day of the week.
Microservices
Many small codebases, each owning one capability (auth, billing, search), each deployed independently, talking to each other over the network.
A mall instead of a big-box store. Each store has its own owner, hours, and inventory. Way more flexibility — and way more hallways and security and rent.
Netflix has hundreds of microservices — one for recommendations, one for video streaming, one for billing. It works because Netflix has thousands of engineers.
When you have enough engineers to staff each service and the org is hurting from teams stepping on each other in one codebase. Rarely the right call for a small company.
Microservices trade code complexity for operational complexity. You now have 40 deploys, 40 databases, 40 alert channels. The bugs move from ‘hard to find in code’ to ‘hard to find across the network.’
Serverless
Code that runs only when it's called and you don't manage the server. You upload a function; the cloud runs it on demand and bills you per invocation. Four ways to render a webpage. CSR = Client-Side Rendering (browser builds the page in JS). SSR = Server-Side Rendering (server builds the HTML per request). SSG = Static Site Generation (HTML built once at deploy time). ISR = Incremental Static Regeneration (static, but refreshes on a schedule).
Uber instead of owning a car. You don't pay for a parked car — you pay when you ride. Great for unpredictable usage, expensive if you ride 24/7.
Supabase Edge Functions. Vercel serverless routes. AWS Lambda. Drivia's generate-course edge function is serverless — it only runs when an intake form is submitted. A blog post — SSG (never changes). A user's dashboard — SSR (personalized per request). Drivia's marketing pages are SSG. The dashboard is SSR.
Bursty or unpredictable workloads. APIs that get 100 requests one hour and 0 the next. Default to static. Use SSR when the page depends on the user. Use ISR when the page is static but needs to update every few minutes (catalog pages).
Cold starts (the first call after idle is slow). Per-invocation pricing that bites at scale. And you're locked into the cloud provider's runtime quirks. SSR / SSG / ISR / CSR React Server Components and the App Router muddy all these terms. The modern answer is 'render the page on the server, stream chunks to the client, hydrate the interactive parts.' Don't fight the framework; learn its model.
CDN (Content Delivery Network)
A network of servers around the world that cache your static content (images, JS, CSS) near your users. A learner in Tokyo hits a CDN node in Tokyo, not your server in Virginia.
Franchise restaurants. McDonald's doesn't fly burgers from Illinois — they have a location near you. CDNs do the same thing with your files.
Cloudflare, Vercel Edge Network, AWS CloudFront. Drivia's static assets and marketing HTML are served from Vercel's CDN.
Always, for static files. The performance gain is free and massive.
Edge computing
Running your code at the CDN level, close to the user — not in a single data center. Latency drops from 200ms to 20ms.
Vercel Edge Functions, Cloudflare Workers. Great for A/B testing, geolocation, and personalization that needs to happen before the page renders.
Load balancer
A traffic cop that spreads incoming requests across multiple servers so no single server gets slammed.
The host at a busy restaurant sending you to an open table. Without them, everyone piles up at one booth and the rest sit empty.
Horizontal vs vertical scaling
Vertical = make the server bigger (more CPU, more RAM). Horizontal = add more servers and share the load.
Vertical: buying a bigger truck. Horizontal: buying more trucks. The bigger truck has a ceiling; more trucks scale forever (until the roads jam).
Default to horizontal. Vertical is a temporary patch; horizontal is the architecture that survives success.
Stateless vs stateful
Stateless = each request contains everything the server needs; the server remembers nothing between requests. Stateful = the server remembers you.
Stateless is a vending machine (put in money, get a snack, it doesn't care who you are). Stateful is your barber (remembers your usual cut).
Stateless web apps scale horizontally with zero effort — any server can handle any request. Push state into the database or Redis, not into the server's memory.
Cache
Temporary storage of expensive-to-compute results so you don't redo the work.
Keeping milk in the fridge instead of driving to the store every time you want cereal. Fast, until the milk goes bad — which is why caches need expiration.
Caching the ‘top 10 lessons’ query result in Redis for 5 minutes. Instead of hitting Postgres 10,000 times per hour, you hit it 12 times.
“There are only two hard problems in computer science: cache invalidation, naming things, and off-by-one errors.” Cache invalidation means: knowing when the cached copy is stale and needs to be refreshed. It is much harder than it looks.
Queue
A list of jobs waiting to be processed, in order. Producers add jobs to the back; workers pull jobs from the front.
A line at the DMV. You take a ticket (produce), you wait, a clerk calls your number (consume). More clerks = faster line.
Drivia's course generation queue. When a Scholars' Day attendee submits the form, the job goes in the queue. A worker picks it up, runs the edge function, and sends the email.
Pub/Sub (Publish/Subscribe)
A pattern where publishers broadcast events and subscribers listen for the ones they care about. The publisher doesn't know who's listening.
A radio station. The DJ plays a song; anyone with a radio can tune in. The DJ doesn't have a list of listeners.
Supabase Realtime. Your app publishes ‘lesson_completed’; the leaderboard, the XP tracker, and the notification service all subscribe and update independently.
Chapter 05 Design Patterns Proven solutions to problems developers keep running into.
MVC (Model-View-Controller)
Split your app into three layers: Model (data and business rules), View (what the user sees), Controller (glue that responds to input).
A restaurant. The kitchen is the model (makes the food). The dining room is the view (where you eat). The waiter is the controller (takes your order, brings the food, handles complaints).
Dependency injection (DI)
Instead of a class creating its own dependencies, you pass them in from outside. Makes testing and swapping providers trivial.
Bad: class EmailService { resend = new Resend() }. Good: class EmailService { constructor(provider) {} }. Now in tests you pass a fake provider; in prod you pass Resend.
Whenever a class talks to the outside world (database, API, file system). DI is how you keep your business logic testable.
Factory
A function whose job is to create objects, often choosing which kind to create based on input.
A createPaymentProvider(country) that returns a Stripe client for most countries, Adyen for Brazil, and a mock for tests.
Singleton
A class where only one instance is ever created, shared everywhere.
A database connection pool. You want one pool, not 500.
Abused constantly. Singletons are global variables in a suit. They make testing painful because tests can't get a fresh one. Use sparingly.
Observer
An object announces when something changes; any other object that cares subscribes and gets notified.
React's useState. When state changes, every component that reads it re-renders. That's the observer pattern — you just don't see the plumbing.
Adapter
A wrapper that makes one interface look like another. Glue between two things that weren't designed to talk.
Your code expects an ILogger interface, but the library you want to use has winston.info(). You write a WinstonAdapter that exposes ILogger and forwards calls to winston.
Middleware
A function that runs between the request and the handler. Each middleware can inspect, modify, reject, or pass the request along.
Airport security. Every passenger walks through it on the way to the gate. Security inspects, stops some, lets most pass.
Next.js middleware that checks for a valid auth cookie before letting the request reach the dashboard. If there's no cookie, redirect to login.
Chapter 06 Data Structures The shapes data takes in memory and why each shape matters.
Array
An ordered list of values, indexed by position. Keys mapped to values, with lightning-fast lookup by key.
A row of numbered mailboxes. You go to mailbox 7 and grab whatever's inside. The index at the back of a textbook. You want ‘photosynthesis’ — you look it up and it tells you page 243. You don't flip through every page.
userIdToProfile[userId] — instant lookup of any user's profile by their ID. Far faster than scanning a list.
When order matters and you mostly access by index or iterate top-to-bottom. Hash map / Hash table / Dictionary When you ask 'give me the thing with this key' a lot. This is 90% of what you'll build with.
access = O(1), insert/delete in middle = O(n) lookup = O(1) average, O(n) worst case
Set
A collection of unique values. No duplicates, no ordering guarantees.
Tracking which lesson IDs a user has completed. Adding the same ID twice is a no-op. Checking if they completed it is instant.
Linked list
A chain of nodes where each node points to the next. Insert and delete are fast; random access is slow.
Rarely, in modern app code. Useful when you're building low-level structures (queues, undo stacks) and need cheap insertion in the middle.
Stack (LIFO)
Last In, First Out. You push things on top and pop things off the top.
A stack of plates. You grab from the top, you add to the top. The plate at the bottom waits forever.
The browser's back button. Every page you visit gets pushed. Back pops the top.
Queue (FIFO)
First In, First Out. You add to the back and take from the front.
A line at Starbucks. First person in line is first to get coffee.
A job queue. Tasks come in, workers pull them in order.
Tree
A hierarchical structure: one root node, each node has children. No cycles.
A family tree. One ancestor at the top, branches below. You can always trace back to the root.
The DOM (HTML document). html is the root; body and head are children; everything nests from there.
Binary search tree
A tree where each node has at most two children, and left children are smaller, right children are larger.
When you need sorted data with fast lookup, insertion, and deletion.
search = O(log n) if balanced, O(n) if degenerate
Graph
Nodes connected by edges. Can have cycles. The most flexible data structure — almost anything in the real world can be modeled as a graph.
Social networks (people are nodes, friendships are edges). Google Maps (cities are nodes, roads are edges). A learning graph in Drivia (concepts are nodes, ‘prerequisite of’ is an edge).
Heap / Priority queue
A queue where the item with the highest (or lowest) priority always comes out first, regardless of insertion order.
An ER waiting room. A heart attack jumps ahead of a sprained ankle, even if the sprain showed up first.
Task schedulers, A* pathfinding, top-K problems ('give me the 10 most recent lessons').
Chapter 07 Algorithms & Big O Notation How fast a thing is when the data grows.
Big O notation
A way to describe how an algorithm's speed scales with input size. It ignores constants and focuses on the shape of the growth.
Finding a user in a hash map = O(1) (instant, doesn't matter if you have 10 or 10 million users). Sorting a list = O(n log n). Checking all pairs in a list = O(n²).
Every code review where someone writes a nested loop over a big table. The question 'what's the Big O of this?' is how you catch a slow query before it hits production.
Big O is about scaling, not raw speed. O(n²) on 10 items is faster than O(n log n) on 10 million. Don't optimize blindly — profile first.
O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2■) < O(n!)
O(1) — Constant time
Same speed no matter how big the input. Hash map lookup, array index, pushing to a stack.
users[userId] takes the same time whether users has 10 or 10 million entries.
O(log n) — Logarithmic time
Doubling the input adds only one more step. Binary search is the classic example.
Guessing a number 1-1000. 'Higher or lower' gets you there in 10 guesses, not 1000. Every guess cuts the remaining range in half.
O(n) — Linear time
Twice the input, twice the work. Scanning a list, printing every element.
array.filter(fn) — has to look at every element.
O(n²) — Quadratic time
Twice the input, four times the work. Usually a nested loop.
Checking every user against every other user for duplicates. 1000 users = 1,000,000 comparisons. Painful on big data.
Binary search
Searching a sorted list by repeatedly halving the remaining range.
lo = 0; hi = arr.length - 1 while (lo <= hi) { const mid = (lo + hi) >> 1; if (arr[mid] === target) return mid; if (arr[mid] < target) lo = mid + 1; else hi = mid - 1; } return -1;
Recursion
A function that calls itself with a smaller version of the problem, until it hits a base case.
Russian nesting dolls. To count them, you count yourself (1), then count the smaller one inside. Eventually you hit the smallest doll with nothing inside — the base case.
Calculating a factorial: factorial(n) = n * factorial(n - 1), with factorial(0) = 1 as the base case.
Forgetting the base case = infinite recursion = stack overflow. Every recursive function needs a clear exit.
Memoization
Caching the result of a function call so the next time you ask with the same input, you skip the work.
The first time someone asks you how to get to the DMV, you figure it out. The second time, you just recite the directions from memory.
Fibonacci without memoization is O(2■). With memoization it's O(n). Same algorithm, massive difference.
Dynamic programming
Breaking a problem into smaller overlapping sub-problems, solving each once, and reusing the answers. Memoization is a form of DP.
Interview questions and certain real problems (pathfinding, diff algorithms, sequence alignment). Not something you do casually in product code.
Greedy algorithm
At every step, pick the best local option and hope it adds up to the best global answer.
Making change for $0.67 with US coins: pick the biggest coin that fits each time. Works for US coins, fails for some other currencies.
Hashing
Converting a chunk of data into a fixed-size number via a deterministic function. Same input → same output, always.
sha256('hello') = 2cf24d.... The output looks random but it's reproducible. Used for hash maps, file integrity checks, passwords, and blockchain.
Chapter 08 Async, Concurrency & Race Conditions How code handles multiple things happening at once.
Synchronous vs asynchronous
Synchronous = one thing at a time, in order. Asynchronous = start something, walk away, come back when it's done.
Synchronous = standing in line at the microwave waiting for your food. Asynchronous = starting the microwave and going back to your desk until it beeps.
Callback
A function you pass to another function, to be called when that function is done. The OG way async worked in JavaScript.
fs.readFile('x.txt', (err, data) => { ... }). When the read finishes, your callback runs.
Callbacks nested in callbacks nested in callbacks = 'callback hell'. Use promises or async/await to flatten it.
Promise
An object that represents a future result — a value that isn't available yet but will be. Syntactic sugar over promises that makes async code look synchronous. async function loadCourse(id) { const course = await db.courses.get(id); const lessons = await db.lessons.forCourse(id); return { course, lessons }; }
A pizza order receipt. The pizza isn't here yet, but the receipt is a promise it will be (or that you'll get your money back).
fetch(url).then(r => r.json()).catch(handleError) async / await
Default for modern JavaScript/TypeScript. Way easier to read and debug than .then() chains.
Race condition
A bug where the outcome depends on the timing of events that shouldn't matter. Two things happen at almost the same time and corrupt each other.
User clicks ‘Save’ twice fast. Both requests read the old profile, both write their version. The second write silently overwrites the first.
To describe the class of bugs that only happen in production, only sometimes, only under load, and nobody can reproduce in dev.
Race conditions are the hardest bugs to debug. The fix is usually a database transaction, an optimistic lock (update where version = X), or a queue.
Deadlock
Two operations each holding a resource the other needs, and neither will let go. Everything freezes.
Two cars at a one-lane bridge, nose-to-nose. Neither backs up. Traffic stops forever.
Transaction A locks row 1 and waits for row 2. Transaction B locks row 2 and waits for row 1. Postgres detects this and kills one of them.
Mutex / Lock
A gate that lets exactly one thread or process hold something at a time. Everyone else waits.
lock.acquire() — do work — lock.release(). If someone else called acquire first, you wait your turn.
Debounce
Wait until a burst of events has stopped before actually running the handler. Typing-triggered searches use this.
A search box that waits 300ms after the user stops typing before hitting the API. Typing 'lesson' triggers one request instead of six.
Search-as-you-type, window resize handlers, form validation on input.
Throttle
Allow a handler to run at most once per time window. Extra calls are dropped (or delayed to the next window).
A scroll handler that runs at most once every 100ms instead of 60 times a second.
Scroll/resize/mouse-move handlers. Rate-limiting API calls from the client.
Backpressure
When a consumer can't keep up with a producer, and you need a way to slow the producer down instead of dropping data or running out of memory.
Your course generation queue is filling up faster than workers can drain it. Backpressure tells the intake endpoint ‘stop accepting new jobs for a minute.’
Chapter 09 Databases Where your data lives and how you talk to it.
SQL vs NoSQL
SQL = tables with strict columns, joined by relationships, queried by a standard language (Postgres, MySQL). NoSQL = looser schemas, documents or key-value pairs, often faster to iterate on but harder to query (MongoDB, DynamoDB, Firestore).
Default to SQL (Postgres specifically). NoSQL is tempting for its schema-less start but punishes you the moment you need relationships, reports, or consistency guarantees.
ACID
Four guarantees a real database gives you: Atomicity (transactions happen fully or not at all), Consistency (valid data in, valid data out), Isolation (concurrent transactions don't see each other's half-done work), Durability (once committed, it survives a crash).
Transferring money between bank accounts. Atomicity means the withdrawal and the deposit either both happen or neither does. Without ACID, you get lost money.
Transaction
A group of database operations that either all succeed or all fail together. BEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT;
Any time two or more writes have to agree on reality.
Index
A sorted lookup structure on one or more columns that makes queries against those columns dramatically faster.
The index at the back of a book. Without it, you read every page to find 'photosynthesis.' With it, you flip to page 243 in one step.
Adding an index on lessons.learning_path_id so pulling all lessons for a course drops from 2 seconds to 2 milliseconds.
Indexes speed up reads but slow down writes (every insert has to update the index). Index the columns you query by, not every column you have.
Query without index: O(n). Query with index: O(log n).
Join
Combining rows from two tables based on a shared column.
Pulling a user and their subscription in one query instead of two. SELECT u.name, s.plan FROM users u JOIN subscriptions s ON s.user_id = u.id WHERE s.plan = 'pro';
Normalization
Organizing data so each fact lives in exactly one place. Reduces storage and prevents update anomalies.
Bad: every orders row stores the customer's address. If they move, you have to update every order. Good: orders has a customer_id; address lives in the customers table once.
Denormalization
Deliberately duplicating data to make reads faster, accepting the cost of keeping copies in sync.
When read performance matters more than write simplicity — analytics dashboards, leaderboards, feeds.
N+1 query problem
A performance bug where loading N items triggers N+1 database queries instead of 1 or 2.
Fetching 100 users, then for each one fetching their profile = 1 + 100 = 101 queries. The fix: a single JOIN or an IN query.
ORMs hide this. You write user.posts.forEach(...) and it looks innocent. Turn on query logging and watch the database scream.
Migration
A script that changes the database schema (add a column, create a table, add an index) in a reproducible, versioned way.
20260411_add_live_intake_source.sql. Run in dev, staging, prod in order. Everyone's database stays in sync.
Every schema change, without exception. Never edit the schema by hand in prod — you lose the audit trail and break every other environment.
RLS (Row-Level Security)
Database-enforced rules that decide which rows a user can see or modify. Policies run inside Postgres; the app can't bypass them.
Drivia's RLS policy: user can only SELECT their own rows from lesson_progress. Even if someone bypasses the app, the database refuses.
Multi-tenant SaaS. RLS is your last line of defense — an app bug should not turn into a data leak.
Connection pool
A shared collection of database connections that the app reuses instead of opening a fresh one for every request.
A pool of 20 Postgres connections. 200 requests coming in? They wait for an idle connection, use it, return it. You don't DOS your own database.
Chapter 10 Networking & APIs How one piece of software talks to another across a wire.
HTTP
The protocol the web runs on. Client sends a request, server sends a response. Methods include GET, POST, PUT, PATCH, DELETE.
REST
A style of API where URLs are resources (/users/123) and HTTP methods are verbs on those resources. GET reads, POST creates, PUT replaces, PATCH updates, DELETE deletes.
GET /api/users/123 returns user 123. POST /api/lessons creates a lesson. DELETE /api/sessions/456 logs out session 456.
GraphQL
A query language where the client says exactly which fields it wants and the server returns just those. One endpoint, flexible shape.
When you have many different clients (mobile, web, partner) all needing different slices of the same data. Overkill for a single web app.
WebSocket
A persistent, two-way connection between client and server. Unlike HTTP (one request, one response, done), a WebSocket stays open so either side can push messages anytime.
Supabase Realtime. Live chat. LiveKit audio/video. JAX streaming tokens as they're generated.
Real-time updates, chat, collaborative editing, live notifications.
Webhook
A URL you hand to someone else so they can POST you an event when something happens on their side. Inverse of an API call.
Stripe POSTs to /api/stripe/webhook when a payment succeeds. You don't have to poll Stripe — Stripe tells you.
Always verify the signature. Webhooks are public URLs; anyone can POST to them unless you cryptographically check the sender.
HTTP status codes
Numbers that tell the client what happened. 2xx = success, 3xx = redirect, 4xx = client's fault, 5xx = server's fault.
200 OK. 201 Created. 301 Moved. 400 Bad Request. 401 Unauthorized. 403 Forbidden. 404 Not Found. 409 Conflict. 429 Too Many Requests. 500 Server Error. 502 Bad Gateway. 503 Service Unavailable.
CORS (Cross-Origin Resource Sharing)
A browser security rule that blocks JavaScript on site A from calling site B, unless site B explicitly allows it with a special header.
If you see 'CORS error' in the browser console, the fix is on the server, not the client. Add Access-Control-Allow-Origin on the API side.
TLS / SSL / HTTPS
The encryption layer on HTTPS. When you see the padlock, TLS is doing its job — the traffic between your browser and the server is unreadable to anyone in the middle.
SSL is the old name; TLS is the current one. ‘SSL certificate’ usually means TLS certificate. No one except pedants cares.
CDN edge caching
When the CDN saves a copy of a response and serves it directly to future users without asking your server.
Cloudflare caches your marketing page. The next 10,000 visitors never touch your server.
Rate limiting
Capping how many requests a client can make in a window. Prevents abuse and protects the service from itself.
100 requests per minute per IP. Beyond that, return 429.
Chapter 11 Security The ways attackers try to break your app, and the words we use to stop them.
Authentication (authn)
Proving who the user is. Username + password, magic link, OAuth.
Don't confuse authentication (who) with authorization (what). Different problems, different solutions.
Authorization (authz)
Deciding what an authenticated user is allowed to do.
User is logged in (authenticated), but they're not a super_admin, so they can't delete other users (authorization check failed).
OAuth / OAuth 2.0
A protocol that lets a user grant one app access to another app's data without sharing their password.
‘Sign in with Google’. Drivia asks Google for a token; Google asks the user to approve; Google hands Drivia a scoped token. Drivia never sees the user's Google password.
JWT (JSON Web Token)
A signed string that encodes a user's identity and claims. The server verifies the signature without hitting the database.
Supabase Auth hands the client a JWT after login. Every subsequent API call includes it in the Authorization header.
JWTs can't be revoked mid-flight (without extra infrastructure). Keep their lifetime short (minutes, not days) and use refresh tokens.
XSS (Cross-Site Scripting)
An attack where the attacker injects JavaScript into your site that runs in another user's browser.
A user sets their bio to <script>steal(cookie)</script>. When another user views the profile, the script runs in their browser with their cookies.
As a reason to escape user content before rendering. React does this by default; dangerouslySetInnerHTML is where XSS creeps in.
CSRF (Cross-Site Request Forgery)
An attack where a malicious site tricks your logged-in user's browser into making a request to your site without their knowledge.
Evil site has <img src=https://bank.com/transfer?to=evil&amount;=1000>. If you're logged into bank.com, the browser happily sends your session cookie with that image request.
When deciding whether to use SameSite=Lax or Strict cookies, or CSRF tokens on form submissions. Modern frameworks handle this for you; know the word so you can verify.
SQL injection
Tricking a database into running attacker-supplied SQL by shoving it into a query string that wasn't properly escaped.
query = 'SELECT * FROM users WHERE name = ' + input. If input is '; DROP TABLE users; --, your users table is gone.
The fix is parameterized queries, always. db.query('... WHERE name = $1', [input]). Never concatenate user input into SQL. Ever.
Hashing vs encryption
Hashing is one-way: you can turn input into a hash, but not the hash back into input. Encryption is two-way: with the key, you can recover the original.
Passwords are hashed (bcrypt, argon2). Your database column never stores the real password, only the hash. Credit card tokens are encrypted because you need to retrieve the original.
Never store passwords plain. Never use MD5 or SHA1 for passwords — they're too fast, which makes cracking them cheap. Use bcrypt or argon2.
Salt
Random data added to a password before hashing so that two users with the same password don't produce the same hash.
Without salt, an attacker can precompute hashes for common passwords (a 'rainbow table') and match yours. With salt, they'd need a table per user.
Secrets management
Keeping API keys, database passwords, and private tokens out of code and out of git history.
Vercel environment variables, AWS Secrets Manager, a .env.local file that is in .gitignore.
Once a secret hits git, assume it's compromised even if you force-push to delete it. Rotate immediately.
OWASP Top 10
A regularly-updated list of the most common web-app vulnerabilities, maintained by the Open Web Application Security Project.
Reviewing code or talking to a security auditor. Everyone in security assumes you've read it.
Chapter 12 Git & Version Control The time machine every developer uses and half of them fear.
Commit
A saved snapshot of the project at a moment in time, with a message explaining what changed and why.
Branch
A separate line of commits. You can experiment on a branch without affecting main, then merge the branch back in when you're happy.
A writer working on a draft of a chapter in a separate document, then pasting the finished version back into the book.
Merge
Combining two branches. Git tries to integrate both sets of changes; if they touched the same lines, you get a merge conflict to resolve.
Rebase
Rewriting your branch's history so it looks like it was built on top of the latest main, instead of branching off an older commit.
Merge is stapling your work onto the book. Rebase is re-typing your chapter as if you'd written it yesterday, after everyone else's edits.
Before merging, to keep history linear. Great for your own branches. Never rebase public branches others have pulled — you rewrite their history.
Squash
Combining a bunch of commits into one. 'Fix typo', 'oops', 'actually fix typo' becomes one clean commit.
Before merging a feature branch, so main stays readable.
Cherry-pick
Copying a single commit from one branch to another, without merging everything else.
Pull request (PR)
A formal request to merge your branch into another branch, usually reviewed by a teammate before it's approved.
You push feature/live-onboarding, open a PR to main on GitHub, a teammate reviews, you address comments, the PR gets merged.
Every change to main in a team setting. PRs are where code quality and culture live.
HEAD
A pointer to the current commit — 'where you are right now' in git.
Detached HEAD
When HEAD points at a specific commit instead of a branch. Anything you commit here is orphaned unless you create a branch from it.
Not a bug, but junior devs panic when they see it. It's just git saying 'you're visiting this commit directly.'
Stash
A temporary drawer for work you haven't committed yet. git stash hides your changes; git stash pop brings them back.
You need to quickly switch branches but aren't ready to commit.
Force push
Overwriting the remote branch with your local version, even if they diverge.
Destroys remote commits. NEVER force-push to main or any shared branch. Fine on your own feature branch after a rebase.
Reflog
Git's private history of where HEAD has been. Saves your life when you think you lost commits.
After any 'oh no' moment. git reflog usually shows the commit you thought you lost, and you can reset to it.
Worktree
Check out a second branch into a separate folder without touching your current working directory.
Chapter 13 CI / CD & Deployment How code goes from your laptop to real users.
CI (Continuous Integration)
Automatically running tests and checks on every push. If the build or tests fail, the PR can't merge.
GitHub Actions running npm run test and npm run build on every PR. Red build = merge blocked.
CD (Continuous Deployment / Delivery)
Delivery = every successful build produces a deployable artifact. Deployment = it also ships to production automatically.
Vercel on Drivia is continuous deployment. Push to main, it's on the internet in 90 seconds. No human in the loop.
Pipeline
An ordered sequence of CI steps — lint, test, build, deploy. Each step depends on the previous one passing.
Lint → unit tests → integration tests → build → deploy to staging → E2E tests → deploy to prod.
Artifact
The thing your build produces that you actually deploy. A compiled binary, a Docker image, a zipped JS bundle.
Whenever you're talking about reproducible deploys. The key rule: the same artifact ships to staging and prod, so you know what you're promoting.
Environment
A distinct copy of your app with its own config: development, staging, production. Same code, different secrets and data.
Dev points at a local Postgres. Staging points at a staging Supabase project. Prod points at the real one. Same Next.js build everywhere.
Staging
A near-identical copy of production used for final testing before a release.
Anytime the cost of a bad prod deploy is bigger than the cost of maintaining staging.
Ephemeral / preview environment
A throwaway environment spun up for each pull request, so reviewers can click around the actual running version of the PR.
Vercel automatically spins up drivia-git-feature-live-onboarding.vercel.app for every PR.
Zero-downtime deploy
A deployment strategy where users never see an error during a rollout. Old version keeps serving traffic until the new version is ready to take over.
Vercel does this by default. The old deployment keeps running until the new one has fully built and passed health checks.
Chapter 14 Observability Knowing what your system is doing, while it's doing it.
Logs
Text lines your code writes describing what happened. The oldest and still the most useful debugging tool.
console.log('[travel/intake] submission saved', id). In production, these land in a log aggregator like Datadog or Logtail.
Metrics
Numerical measurements over time. ‘Requests per second,’ ‘average response time,’ ‘error rate.’
Grafana showing Drivia's request latency over the last 24 hours. You see a spike at 3am and know when to dig into logs.
Traces
A record of a single request's entire journey across your system — every function it touched, every database query, every external call, and how long each took.
OpenTelemetry traces show you the Scholars' Day intake took 240ms, of which 180ms was waiting on auth.admin.listUsers. Now you know where to optimize.
Debugging slow requests across microservices or edge functions where logs alone lose the thread.
SLA / SLO / SLI
SLA = Service Level Agreement, the contract with the customer ('99.9% uptime or your money back'). SLO = Service Level Objective, your internal target ('99.95% uptime'). SLI = Service Level Indicator, the thing you actually measure (uptime percentage from a probe).
Drivia promises 99.9% uptime (SLA). Internally we target 99.95% (SLO) so we have buffer. We measure uptime via synthetic probes every 60 seconds (SLI).
Error budget
How much failure you can tolerate before breaking your SLO. A 99.9% SLO gives you ~43 minutes of downtime per month — that's your budget.
To decide whether to slow down and fix reliability (burning budget fast) or speed up and ship features (budget healthy).
Alert
An automated message that fires when a metric crosses a threshold — paging oncall, sending a Slack ping, opening a ticket.
On-call
A rotation where one engineer is the first responder for production incidents, usually for a week at a time.
Postmortem
A blameless written report after an incident. Timeline of what happened, what was broken, what the fix was, and how to prevent it next time.
Blameless is the key word. The goal is to fix the system, not the person.
MTTR / MTBF
MTTR = Mean Time To Recover (how fast you get back up). MTBF = Mean Time Between Failures (how often you go down). The goal is high MTBF, low MTTR.
Chapter 15 AI / ML Vocabulary The words every CTO building on LLMs needs to speak fluently.
LLM (Large Language Model)
A neural network trained on billions of words that can predict the next token given previous tokens. GPT-4, Claude, Gemini, Llama.
JAX in Drivia is an LLM wrapped in a system prompt, a retrieval layer, and the H2E adaptive context.
Token
The unit an LLM processes. Not quite a word, not quite a letter — somewhere in between. 'hello' is 1 token; 'unbelievable' might be 3.
A 500-word lesson is roughly 650 tokens. An 8k-token context window can hold about 6,000 words of conversation + system prompt + retrieved context.
~1 token = 4 characters of English = ¾ of a word
Context window
The maximum number of tokens a model can consider at once — prompt + response combined.
GPT-4 Turbo: 128k tokens (~96,000 words). Claude: 200k+. If you exceed it, the model forgets the oldest parts.
Designing prompts. If your prompt + expected response is near the limit, you need a retrieval strategy.
Prompt engineering
The craft of writing model inputs (system prompts, user prompts, examples) to get reliable outputs.
Telling JAX ‘You are an expert tutor. Respond in 3 sentences. Address the student by name.’ gets very different outputs than ‘help’.
System prompt
The instructions you give the model before the user's message. Sets persona, rules, tone, and constraints.
Drivia's JAX system prompt includes the H2E context, Wilson's voice, and the rule 'never break character.'
Fine-tuning
Training a pre-trained model further on your own data so it specializes in your task or voice.
When prompt engineering isn't enough and you have thousands of high-quality examples. Expensive; usually prompt engineering + RAG beats fine-tuning in cost and flexibility.
RAG (Retrieval-Augmented Generation)
Before calling the LLM, look up relevant documents from a database and stuff them into the prompt. The model answers with fresh, specific knowledge you didn't have to train it on.
A student asks JAX 'what's on slide 3 of today's lesson?' Drivia retrieves the lesson content from Postgres, pastes it into the prompt, and JAX answers from real context instead of hallucinating.
Anytime the answer lives in your own data. RAG is the default pattern for ‘AI that knows my stuff.’
Embedding
A fixed-length vector of numbers that represents the meaning of a piece of text. Similar meaning = similar vectors.
GPS coordinates for ideas. 'puppy' and 'dog' are close together; 'puppy' and 'calculus' are far apart.
Drivia turns every lesson into an embedding and stores them in a vector index. When a student asks a question, we embed the question and find the closest lessons by vector distance.
similarity = cosine(vec_A, vec_B), range [-1, 1]
Vector database
A database optimized for similarity search over embeddings. Pinecone, pgvector, Weaviate, Chroma.
Drivia uses pgvector, Postgres's vector extension, so the embeddings live next to the rest of the data.
Hallucination
When an LLM confidently makes up facts that aren't true. It's not lying; it's generating statistically plausible text without a grounding in reality.
The #1 product risk with LLMs. Mitigations: RAG, citations, 'I don't know' prompting, and user-facing disclaimers.
Temperature
A knob on LLM generation. Low (0.0) = deterministic, boring, factual. High (1.5+) = creative, varied, weird.
Temperature 0.2 for factual answers. Temperature 0.7-0.9 for creative writing. Match it to the task.
Chain of thought (CoT)
Asking the model to show its reasoning step by step before the final answer. Reasoning visibly often beats reasoning in one shot.
'Solve this problem step by step. Then give the answer.' Accuracy on math problems jumps dramatically.
Function calling / Tool use
A feature where the LLM can request a structured call to an external tool (database, API, calculator) and use the result in its answer. Training method where humans rank model outputs, and the model learns to prefer the highly-ranked kind. How ChatGPT became agreeable.
JAX calls getStudentProgress(userId), gets a real number, and uses it in the response instead of making one up. RLHF (Reinforcement Learning from Human Feedback)
To explain why modern LLMs are so polite. It's not natural — it was trained in.
Zero-shot / Few-shot / Many-shot
Zero-shot = ask the model directly with no examples. Few-shot = include a handful of examples in the prompt. Many-shot = dozens of examples.
Few-shot is usually the sweet spot for niche tasks where the model needs a pattern to match.
Distillation
Training a small, fast model to imitate a big, slow one. You get most of the quality at a fraction of the cost.
DeepSeek-R1-Distill-Llama-70B, mentioned on your Scholars' Day slide 13, is a smaller model trained to imitate DeepSeek-R1's reasoning.
Quantization
Compressing a model's weights to smaller numbers (int8, int4) so it runs faster on cheaper hardware, with a small quality hit.
Qwen2.5-3B-Instruct-AWQ on your RunPod endpoint — AWQ is a quantization method. A 3B-parameter model runs on a single consumer GPU.
Chapter 16 Team & Process Vocabulary The words engineering teams use to coordinate and decide.
MVP (Minimum Viable Product)
The smallest version of a product that delivers real value to real users and lets you learn whether anyone cares. Not 'cheap' — 'minimum to learn.'
MVP is not 'shoddy and incomplete.' It's 'ruthlessly scoped and actually good at the one thing it does.'
Spike
A time-boxed investigation to answer a technical question before committing to a plan. 'Can we actually do this in 2 days?' 'Is this library fast enough?'
RFC (Request for Comments)
A written proposal for a technical change, circulated for team feedback before building anything. Forces clarity and catches objections cheap.
Any change that affects more than one person's area. 'I'm going to restructure the billing service' — write the RFC, circulate, discuss, then build.
Code review
Another engineer reads your PR and comments before it merges. The single highest-ROI activity in engineering culture.
Good reviews are kind, specific, and focused on impact. Bad reviews are pedantic, ego-driven, or a rubber stamp. Train the team on what a good review looks like.
Sprint
A fixed time-box (usually 1-2 weeks) in which a team commits to a set of work and ships it.
Standup
A short daily meeting where each engineer says what they did yesterday, what they'll do today, and what's blocking them.
Easy to ruin by turning into status theater for a manager. Keep it under 10 minutes and focused on unblocking each other.
Retrospective (retro)
A team meeting at the end of a sprint or project to talk about what went well, what didn't, and what to change.
After anything that mattered — a launch, an outage, a finished sprint. The team that runs good retros gets better. The team that skips them plateaus.
Backlog
The prioritized list of work waiting to be done.
Kanban
A work management style where tasks flow across columns (To Do → In Progress → Review → Done) and you cap work-in-progress to avoid chaos.
Paging / oncall rotation
The practice of having one engineer on-call 24/7 to respond to production incidents, usually rotating weekly.
SEV (Severity)
How bad an incident is. SEV-1 = complete outage, all hands. SEV-4 = minor, fix during business hours. You don't need to memorize this. You need to recognize patterns. When a developer on your team says ‘we're hitting an N+1 on the lessons query,’ you already know the shape of the problem (too many queries), the shape of the fix (JOIN or batch fetch), and the right follow-up question ('is this blocking a user-facing page or a background job?'). That recognition is what separates a CTO who leads engineers from a CTO who gets led. The vocabulary is the thin end of the wedge — knowing the word gives you the permission to ask the right question. The right question gets you the real answer. Keep this open next to your editor. Come back to sections when real code forces them on you. The terms will stop being words and start being instincts — and when they do, you'll be running the best engineering org UMHB ever produced. — Your terminal AI, 2026-04-11