master practice guide · milestone-mapped edition

ONE MILESTONE
ONE EXERCISE

A direct companion to the technology guides. Every section uses the exact milestone order from its source guide, and every milestone has one primary exercise, two follow-up drills, and a narrow reference when the concept still is not sticking.

Mapping rule: milestone 01 → exercise 01, milestone 02 → exercise 02, and so on. No regrouping by project phase and no substitute concepts.
Godot + GDShader
One exercise for every milestone, in the same order and wording
01
Milestone 01 · exact map
3D scene setup — nodes and their roles
Source milestone

Set up a 3D scene: MeshInstance3D, Camera3D, WorldEnvironment — understand what each node's job is

Primary exercise
Create a 3D scene from scratch. Add a MeshInstance3D with a SphereMesh, a Camera3D pointing at it, and a WorldEnvironment with a sky. Run it. Then rename every node to something descriptive and write a one-line comment in a GDScript attached to the root explaining what each child does.
Still stuck?

Godot Docs — 3D scene introduction. Search 'your first 3D game' in the docs.

02
Milestone 02 · exact map
First fragment shader — UV coordinates
Source milestone

Attach a ShaderMaterial and write a fragment shader that colors by UV coordinates

Primary exercise
Attach a ShaderMaterial to your sphere. Write a fragment shader that sets ALBEDO to vec3(UV, 0.0). Run it. Draw on paper what you expect the colors to be before you run it, then compare. Change it to vec3(UV.x, 0.0, 0.0) and predict the result before running.
Still stuck?

The Book of Shaders chapter 1–2. Godot shading language docs — search 'UV'.

03
Milestone 03 · exact map
Per-vertex vs per-fragment distinction
Source milestone

Understand the difference between what runs per-vertex and what runs per-fragment

Primary exercise
Write a shader with both a vertex() and fragment() function. In vertex(), add a small offset to VERTEX.y based on VERTEX.x — this deforms the mesh. In fragment(), color by UV. Run it and observe which stage controls shape vs color. Then try moving the color logic into vertex() via a varying — does it look different? Why?
Still stuck?

Book of Shaders chapter 3. Search 'varying' in the Godot shading language docs.

04
Milestone 04 · exact map
Uniforms driven from a GDScript
Source milestone

Use TIME and uniforms to animate shader parameters driven from a GDScript

Primary exercise
Add a uniform float brightness = 1.0 to your shader. In fragment(), multiply ALBEDO by brightness. Attach a GDScript to the MeshInstance3D. In _ready(), get the ShaderMaterial and set the brightness uniform to 2.0. Confirm the sphere is brighter. Then in _process(), oscillate brightness with sin(Time.get_ticks_msec() * 0.001).
Still stuck?

Godot docs — search 'ShaderMaterial set_shader_parameter'. GDScript signals docs.

05
Milestone 05 · exact map
Diffuse and specular lighting by hand
Source milestone

Implement diffuse and specular lighting by hand in the fragment stage — no built-in lighting nodes

Primary exercise
On a flat PlaneMesh, implement Lambertian diffuse from scratch. You need: the surface normal (NORMAL), a hardcoded light direction vec3, and the dot product between them clamped to 0. Set ALBEDO to vec3(diffuse). No engine lighting nodes. Then add a hardcoded light color and multiply it in.
Still stuck?

Book of Shaders chapter 5. Search 'Phong lighting model' on learnopengl.com — the math is identical even though it's OpenGL.

06
Milestone 06 · exact map
MVP matrix — what each transform does
Source milestone

Understand the MVP matrix: what model, view, and projection each do to a vertex

Primary exercise
In a vertex shader, print MODELVIEW_MATRIX and PROJECTION_MATRIX to understand what's available. Then manually break the pipeline: multiply VERTEX by only the model matrix (MODEL_MATRIX * vec4(VERTEX, 1.0)) and assign to POSITION — observe what happens. Restore it. Write a note explaining what each matrix's job is in one sentence each.
Still stuck?

learnopengl.com — 'Coordinate Systems' chapter. Direct mapping to Godot's matrix uniforms.

07
Milestone 07 · exact map
Vertex deformation with noise
Source milestone

Deform geometry in the vertex stage — displace vertices with a noise function

Primary exercise
On a PlaneMesh with enough subdivisions (at least 32x32), displace VERTEX.y in the vertex stage using a sin wave: VERTEX.y += sin(VERTEX.x * 3.0 + TIME) * 0.2. Run it. Then replace the sin wave with a more complex noise pattern by combining two sin waves with different frequencies.
Still stuck?

Book of Shaders chapter 5 — noise. Godot docs — 'vertex() built-in variables'.

08
Milestone 08 · exact map
SubViewport as a render target
Source milestone

Use SubViewport as a render target — render a scene into a texture, use that texture in another shader

Primary exercise
Create a SubViewport node with its own Camera3D and a rotating mesh inside it. Add a ViewportTexture referencing this SubViewport to a material on a quad in your main scene. You are now rendering one scene into a texture and displaying that texture in another scene. Confirm it works — the quad should show the rotating mesh.
Still stuck?

Godot docs — 'Using a Viewport as a texture'. Search 'ViewportTexture'.

09
Milestone 09 · exact map
Compute shader via RenderingDevice — dispatch and readback
Source milestone

Write a compute shader via RenderingDevice — dispatch a workgroup, write to a buffer, read it back

Primary exercise
Write a compute shader that doubles every value in a float array. On the CPU side: create a RenderingDevice, create a storage buffer with [1.0, 2.0, 3.0, 4.0], dispatch 4 threads (one per element), read the buffer back, print the result. Confirm you get [2.0, 4.0, 6.0, 8.0]. This proves the pipeline works before you make it complex.
Still stuck?

Godot docs — 'Using compute shaders'. Search 'RenderingDevice' in the docs.

10
Milestone 10 · exact map
GPU particle system via compute
Source milestone

Build a GPU particle system: particle state lives in a storage buffer, updated entirely by compute

Primary exercise
Define a particle struct in your compute shader with position (vec2), velocity (vec2), and lifetime (float). Store N particles in a storage buffer. Each dispatch call: move position by velocity, decrement lifetime, respawn if lifetime <= 0. On the CPU: initialize particles, dispatch each frame, render positions as points using immediate geometry or a shader.
Still stuck?

Acerola on YouTube — search 'compute shader particles'. Godot RenderingDevice docs.

11
Milestone 11 · exact map
Multi-pass pipeline — geometry into lighting into post
Source milestone

Implement a multi-pass pipeline: geometry pass → lighting pass → post-processing stack

Primary exercise
Set up three SubViewports in sequence. Viewport 1: renders your scene normally into a texture. Viewport 2: takes that texture as input, applies a grayscale conversion in a fragment shader, outputs to another texture. Viewport 3: takes viewport 2's output and applies a vignette. Display the final result. Confirm each pass is doing what you expect by temporarily displaying each intermediate texture.
Still stuck?

Acerola on YouTube — search 'post processing'. Godot docs 'multiple render targets'.

12
Milestone 12 · exact map
What Godot is abstracting from you
Source milestone

Understand what Godot is abstracting from you that Vulkan would force you to manage explicitly

Primary exercise
Write a list of at least five things Godot handles that you would have to manage manually in raw Vulkan. For each one, write a sentence describing what the manual version would require. Examples to get you started: swap chain management, render pass creation, pipeline state objects. This is a thinking exercise, not a coding one.
Still stuck?

vulkan-tutorial.com — skim the 'Drawing a triangle' section to feel the contrast. No need to implement it yet.

C++
One exercise for every milestone, in the same order and wording
01
Milestone 01 · exact map
Stack vs heap — what lives where
Source milestone

Understand the stack and heap: what lives where, why it matters, and what a stack frame is

Primary exercise
Write a function that declares an int on the stack and returns a pointer to it. Call it from main and print the value through the pointer. Observe that the value is garbage or seemingly correct but undefined. Fix it by allocating on the heap instead. Then fix it again using a return by value. Write a comment explaining why each version behaves as it does.
Still stuck?

learncpp.com — 'Stack and Heap Memory'. Chapter 12.

02
Milestone 02 · exact map
Pointers, references, and dangling pointers
Source milestone

Pointers and references: when to use each, what a dangling pointer looks like, and how to avoid one

Primary exercise
Write four versions of a function that increments an integer: taking by value, by pointer, by reference, and returning a new value. Call each from main and confirm which ones actually modify the original. Then write a function that returns a reference to a local variable — compile with warnings enabled and read what the compiler tells you.
Still stuck?

learncpp.com — 'Introduction to Pointers' and 'Dangling Pointers'. A Tour of C++ chapter 1.

03
Milestone 03 · exact map
RAII — constructor acquires, destructor releases
Source milestone

RAII: why resource acquisition in the constructor and release in the destructor is the correct model

Primary exercise
Write a FileHandle class. The constructor takes a filename string and opens the file with fopen. The destructor calls fclose. Delete the copy constructor and copy assignment operator. Use it in a function that opens a file, reads one line, and returns — confirm the file is closed when the function returns by adding a print to the destructor.
Still stuck?

learncpp.com — 'Introduction to RAII'. A Tour of C++ chapter 5.

04
Milestone 04 · exact map
Rule of 0/3/5
Source milestone

Rule of 0/3/5: when the compiler-generated versions are wrong and what to write instead

Primary exercise
Write a Buffer class that holds a raw int* allocated in the constructor. Write a main that copies one Buffer to another (Buffer b2 = b1). Run it and observe what happens when both destructors run. Fix it by implementing the rule of 3: a destructor, copy constructor, and copy assignment operator that each do the right thing. Then add move semantics (rule of 5) and confirm the move leaves the source in a valid empty state.
Still stuck?

learncpp.com — 'The Rule of Three/Five/Zero'. A Tour of C++ chapter 6.

05
Milestone 05 · exact map
Smart pointers — unique_ptr, shared_ptr, weak_ptr
Source milestone

Smart pointers: unique_ptr for single ownership, shared_ptr for shared ownership, weak_ptr for observation

Primary exercise
Rewrite your Buffer class exercise using unique_ptr<int[]> instead of a raw pointer. Observe that you don't need to write the destructor, copy constructor, or copy assignment — they're automatically correct. Then try to copy the unique_ptr and read the compile error. Fix it with std::move and observe that the source is now null.
Still stuck?

learncpp.com — 'Smart Pointers'. A Tour of C++ chapter 13.

06
Milestone 06 · exact map
Move semantics — what a move actually does
Source milestone

Move semantics: what a move actually does at the memory level and why it matters for performance

Primary exercise
Write a class with a constructor, destructor, copy constructor, move constructor, and print statements in each. Create an instance, copy it, move it. Read the output and trace exactly which operations fired. Then write a function that returns an instance of your class and observe whether a copy or move fires (or neither, due to RVO).
Still stuck?

learncpp.com — 'Move Semantics'. A Tour of C++ chapter 6.

07
Milestone 07 · exact map
Classes, virtual dispatch, and the vtable
Source milestone

Classes, inheritance, and virtual dispatch: what the vtable is and what it costs

Primary exercise
Write a Shape base class with a virtual float area() method. Derive Circle and Rectangle from it. Store both in a vector<Shape*> and call area() through the base pointer. Confirm polymorphism works. Then add a non-virtual method to Shape and override it in Circle — call it through a Shape* and observe that the base version runs. Write a comment explaining why.
Still stuck?

learncpp.com — 'Virtual Functions'. A Tour of C++ chapter 4.

08
Milestone 08 · exact map
Templates and the STL
Source milestone

Templates and the STL: vector, map, unordered_map, iterators, algorithms — use them, don't rewrite them

Primary exercise
Write a generic min function template that works for int, float, and std::string. Then use std::sort on a vector<int> with the default comparator, then with a custom lambda that sorts in reverse. Then use std::find_if to find the first element greater than 5. Write a note on what the algorithm is doing and what the iterator abstraction is giving you.
Still stuck?

learncpp.com — 'Templates' and 'Standard Library Containers'. A Tour of C++ chapters 8–9.

09
Milestone 09 · exact map
const-correctness
Source milestone

const-correctness: what it means for a method to be const and why it matters

Primary exercise
Write a Point class with x and y members and a toString() method. Compile this: const Point p{1, 2}; std::cout << p.toString(); — observe the error. Fix it by marking toString() as const. Then write a function that takes a const Point& and calls a non-const method on it. Fix that too. Write a note on what const on a method guarantees.
Still stuck?

learncpp.com — 'Const class objects and member functions'.

10
Milestone 10 · exact map
Build systems — CMake, translation units, linking
Source milestone

Build systems: what CMake does, what a compilation unit is, how linking works

Primary exercise
Create a CMakeLists.txt that builds a two-file project: main.cpp and math.cpp, with math.h as the header. Define an add function in math.cpp, declare it in math.h, include the header in main.cpp, call the function. Build it with cmake and make. Then deliberately create a linker error by removing math.cpp from the CMakeLists.txt and read the error message.
Still stuck?

learncpp.com — 'Introduction to the compiler, linker, and libraries'. CMake docs tutorial.

11
Milestone 11 · exact map
Multithreading — threads, mutex, atomics
Source milestone

Multithreading: std::thread, mutex, atomic — what a data race looks like and how to prevent one

Primary exercise
Write a program that increments a shared int counter 1,000,000 times across two threads without any synchronization. Run it multiple times and record the results — they should vary. Then add a std::mutex and lock it around the increment. Run again — the result should now always be 2,000,000. Finally replace the mutex with std::atomic<int> and compare the performance.
Still stuck?

learncpp.com — 'Introduction to threads'. A Tour of C++ chapter 18.

12
Milestone 12 · exact map
Undefined behavior — causes and reasoning
Source milestone

Undefined behavior: the three most dangerous causes and how to reason about them

Primary exercise
Write three programs that each contain a different form of undefined behavior: (1) out-of-bounds array access, (2) signed integer overflow, (3) reading an uninitialized variable. Compile each with and without -fsanitize=address,undefined. Observe what the sanitizers catch that a plain run might not. Write a comment on each explaining why the behavior is undefined rather than defined as an error.
Still stuck?

learncpp.com — 'Undefined behavior'. Search 'UB and compiler optimizations' for the deeper rabbit hole.

13
Milestone 13 · exact map
Graphics pipeline from C++ — draw call to pixels
Source milestone

The graphics pipeline from C++: how a draw call gets from your code to pixels — buffers, shaders, the GPU driver

Primary exercise
In a minimal OpenGL or Vulkan setup (learnopengl.com has the scaffold), trace a single triangle from definition to pixels: define vertices in a C++ array, upload to a VBO, write a minimal vertex shader that passes position through, write a minimal fragment shader that outputs red, issue a draw call, observe the red triangle. Comment each step with what layer of the pipeline it corresponds to.
Still stuck?

learnopengl.com — 'Hello Triangle'. This is the primary resource for this milestone.

14
Milestone 14 · exact map
Low-level I/O — binary data and hardware
Source milestone

Low-level I/O: reading and writing binary data, talking to hardware at the byte level

Primary exercise
Write a program that reads a BMP or WAV file in binary mode (fopen with 'rb'). Parse the header manually by casting the raw bytes into a struct. Print the width, height, and bit depth (BMP) or sample rate and channel count (WAV). Write the raw pixel or sample data back out to a new file. Confirm the output file is valid.
Still stuck?

learncpp.com — 'File I/O'. Search 'binary file I/O C++' for struct-level parsing examples.

Lua Embedding
One exercise for every milestone, in the same order and wording
01
Milestone 01 · exact map
Tables — array, map, and object simultaneously
Source milestone

Tables: the only data structure — understand how one table represents an array, a map, and an object simultaneously

Primary exercise
In the Lua REPL, create a table that functions as all three things at once: integer keys 1–3 (array part), string keys 'name' and 'age' (map part), and a function value 'greet' (object part). Access each one. Then iterate it with ipairs and pairs and write down what each iterator visits and what it skips.
Still stuck?

Programming in Lua chapter 2 — Tables.

02
Milestone 02 · exact map
Functions as first-class values and closures
Source milestone

Functions as first-class values: closures, upvalues, and why functions in Lua are just values

Primary exercise
Write a make_counter function that returns a function. Each call to the returned function increments and returns a count. Create two counters and confirm they maintain independent state. Write a comment explaining what the closure is capturing and why each counter is independent.
Still stuck?

Programming in Lua chapter 6 — Closures.

03
Milestone 03 · exact map
Metatables and __index — OOP and inheritance
Source milestone

Metatables and __index: how Lua fakes OOP and inheritance without a class keyword

Primary exercise
Build a minimal class system. Write a function new_class() that returns a class table with a new() method. new() should create an instance table whose metatable's __index points to the class — so instances inherit class methods. Add a method greet to the class. Create two instances and confirm they both have access to greet without it being copied onto each instance.
Still stuck?

Programming in Lua chapter 16 — OOP. Chapter 13 — Metatables.

04
Milestone 04 · exact map
Coroutines — yield and resume
Source milestone

Coroutines: cooperative multitasking, yield and resume, when they're useful vs threads

Primary exercise
Write a coroutine that generates Fibonacci numbers. Each call to coroutine.resume should yield the next number. Call it 10 times from a loop and collect the results. Then write a note explaining why this is different from calling a function 10 times — what state is being preserved between resumes?
Still stuck?

Programming in Lua chapter 9 — Coroutines.

05
Milestone 05 · exact map
Multiple return values and varargs
Source milestone

Multiple return values and varargs: patterns that don't exist in most languages

Primary exercise
Write a function that returns three values: the min, max, and average of a table of numbers. Call it and capture all three return values. Then write a wrapper that takes varargs (...), packs them into a table with table.pack, and passes the table to your min/max/average function.
Still stuck?

Programming in Lua chapter 5 — Functions.

06
Milestone 06 · exact map
The C API stack model — pushing and popping
Source milestone

The C API stack model: how Lua and C exchange values by pushing and popping a shared stack

Primary exercise
In a minimal C program with Lua embedded, write a C function that takes two numbers from the Lua stack, adds them, and pushes the result back. Register it as 'add' in Lua. Call it from a Lua script: assert(add(3, 4) == 7). Print the stack size before and after your operations to confirm you're leaving it clean.
Still stuck?

Programming in Lua chapter 27 — The C API. Lua reference manual — lua_State and stack operations.

07
Milestone 07 · exact map
Registering C functions for Lua to call
Source milestone

Registering a C function so Lua scripts can call into your C++ host

Primary exercise
Register at least three C functions into a Lua namespace: engine.log(msg), engine.get_time(), and engine.set_color(r, g, b). Call all three from a Lua script. Confirm each one runs. Then register them as a library using luaL_newlib rather than setting globals one by one.
Still stuck?

Programming in Lua chapter 28 — Extending Lua. Lua reference manual — luaL_newlib.

08
Milestone 08 · exact map
Calling a Lua function from C++
Source milestone

Calling a Lua function from C++: loading a script, pushing arguments, calling, reading results

Primary exercise
Load a Lua script from a file that defines a function behavior(x, y) which returns x * 2 + y. From C++: open a Lua state, load the file, push the function name, push two numbers as arguments, call it with lua_pcall, and read the return value off the stack. Print it. Change the Lua file and rerun without recompiling C++.
Still stuck?

Programming in Lua chapter 25 — Calling Lua functions from C. Lua reference manual — lua_pcall.

09
Milestone 09 · exact map
Embedding the Lua interpreter in C++
Source milestone

Embedding the Lua interpreter in a C++ program: luaL_newstate, opening libs, loading files

Primary exercise
Write a complete minimal embedding: luaL_newstate, luaL_openlibs, load a script with luaL_dofile, run it, close with lua_close. The script should print 'hello from Lua'. Then extend it: the script defines a config table, your C++ code reads three values from it using lua_getglobal and lua_getfield. Print them from C++.
Still stuck?

Programming in Lua chapter 24 — An Overview of the C API. Lua reference manual — luaL_newstate.

TypeScript + Vue
One exercise for every milestone, in the same order and wording
01
Milestone 01 · exact map
What TypeScript adds — the compiler's job
Source milestone

Understand what TypeScript adds over JavaScript — what the compiler catches and why that matters

Primary exercise
Write a function greet(name) in plain JavaScript and call it with a number. It works. Now add TypeScript types: greet(name: string): string. Call it with a number. Read the error. Then introduce an intentional type mismatch in a variable assignment and read that error. Write down in plain English what the compiler caught and why JavaScript wouldn't have.
Still stuck?

TypeScript Handbook — 'The Basics'. typescriptlang.org/docs/handbook/2/basic-types.html

02
Milestone 02 · exact map
Types, interfaces, unions, and type aliases
Source milestone

Core types: string, number, boolean, arrays, tuples, and the difference between type and interface

Primary exercise
Write a type alias for a UserId (string). Write an interface for a User with id, name, email, and an optional age. Write a union type Status = 'active' | 'inactive' | 'pending'. Write a function that takes a User and returns a Status. Then write a second interface AdminUser that extends User with a permissions string array.
Still stuck?

TypeScript Handbook — 'Object Types' and 'Union Types'.

03
Milestone 03 · exact map
Type narrowing
Source milestone

Union types and type narrowing — how TypeScript knows what something is inside a conditional

Primary exercise
Write a function that takes string | number. Inside it, use typeof to narrow to each branch and perform a type-specific operation (toUpperCase for string, toFixed for number). Then write a function that takes an Animal interface and a Dog interface that extends it — use the 'in' operator to narrow to Dog and access a dog-specific property.
Still stuck?

TypeScript Handbook — 'Narrowing'.

04
Milestone 04 · exact map
Optional chaining and null handling
Source milestone

Optional chaining and nullish coalescing — handling null and undefined without explosion

Primary exercise
Create a nested object type: User with an optional address, address with an optional city. Create instances where address is present, where it's undefined, and where address exists but city is undefined. Access city safely using optional chaining for all three. Then use nullish coalescing to provide a 'Unknown City' default.
Still stuck?

TypeScript Handbook — 'Everyday Types' — null and undefined section.

05
Milestone 05 · exact map
Generics at a working level
Source milestone

Generics at a working level — read and write generic functions without needing the deepest corners

Primary exercise
Write a generic identity function <T>(value: T): T. Call it with a string, a number, and an object. Observe the inferred types. Then write a generic first<T>(arr: T[]): T | undefined that returns the first element of any array. Write a generic Pair<A, B> type and a function that swaps the two values.
Still stuck?

TypeScript Handbook — 'Generics'.

06
Milestone 06 · exact map
Async/await and Promises
Source milestone

Async/await and Promises — the event loop mental model and how async functions compose

Primary exercise
Write three async functions that each resolve after a delay (use setTimeout wrapped in a Promise). Call them sequentially with await and measure the total time. Then call them with Promise.all and measure again. Write a comment explaining why Promise.all is faster and what it's doing differently.
Still stuck?

TypeScript Handbook — 'Everyday Types' async section. MDN — 'Using Promises'.

07
Milestone 07 · exact map
Fetch and typed API responses
Source milestone

Fetch and typed API responses — model a REST response as an interface, type the data end to end

Primary exercise
Fetch data from https://jsonplaceholder.typicode.com/users. Write a User interface that matches the response shape. Assign the parsed response to User[] — no 'any'. Access user.name and user.email through the typed response. Then write a fetchUser(id: number): Promise<User> function that fetches a single user by ID.
Still stuck?

TypeScript Handbook — 'Everyday Types'. MDN — Fetch API.

08
Milestone 08 · exact map
Vue single-file components — script setup, template, props
Source milestone

Vue single-file components — the <script setup>, <template>, and <style> structure and what each section does

Primary exercise
Create a UserCard.vue single-file component with <script setup lang="ts"> that accepts a User prop (your interface from above) and renders name, email, and age. Create a UserList.vue that accepts User[] as a prop and renders a UserCard for each with :key. Then write a HighlightCard.vue that wraps UserCard and adds a colored border prop — define both the User fields and the extra color string in defineProps.
Still stuck?

Vue docs — vuejs.org/guide/components/props and vuejs.org/guide/components/slots.

09
Milestone 09 · exact map
ref, reactive, and computed
Source milestone

ref and reactive — Vue's reactivity primitives, when to use each, and how computed() derives from them

Primary exercise
Write a counter component using <script setup lang="ts">. Use ref<number>(0) for the count. Add increment, decrement, and reset functions bound to buttons with @click. Then add a watch that logs the count whenever it changes. Then add a watchEffect that sets document.title to the current count. Observe that watchEffect runs immediately on mount without needing an explicit dependency list.
Still stuck?

Vue docs — vuejs.org/guide/essentials/reactivity-fundamentals and vuejs.org/guide/essentials/watchers.

10
Milestone 10 · exact map
Loading, error, and success states with Vue composables
Source milestone

watchEffect and watch — reacting to reactive state changes, the equivalent of useEffect in Vue

Primary exercise
Write a Vue composable useUser(id: number) in a separate useUser.ts file. Inside it, use ref for the user data, a loading boolean ref, and an error ref. Fetch on call, set each ref appropriately. Return all three. Consume the composable in a component and render each state explicitly with v-if/v-else-if/v-else. Add a retry button that re-calls the fetch function.
Still stuck?

Vue docs — vuejs.org/guide/reusability/composables. TypeScript Handbook — discriminated unions.

11
Milestone 11 · exact map
Vite and build tooling
Source milestone

Vite as a build tool — what a bundler does, what npm scripts are, how modules resolve

Primary exercise
Create a new project with npm create vite@latest — choose React + TypeScript. Run it. Then inspect the vite.config.ts file and write a comment explaining what each option does. Add a path alias (e.g., @components maps to ./src/components). Import a component using the alias and confirm it resolves.
Still stuck?

Vite docs — vitejs.dev/guide. TypeScript Handbook — 'Module Resolution'.

12
Milestone 12 · exact map
Props, emits, and typed component communication
Source milestone

Props and emits — passing typed data down, emitting typed events up, and when prop drilling becomes a problem

Primary exercise
Build a three-level component tree: App → UserList → UserCard. Pass a User[] down through typed props at each level using defineProps<{ users: User[] }>(). Add a select emit: in UserCard, use defineEmits<{ select: [user: User] }>() and emit it on click. Bubble it up through UserList to App where it's displayed. Count how many components the emit passes through unnecessarily.
Still stuck?

Vue docs — vuejs.org/guide/components/provide-inject and vuejs.org/guide/components/events.

Go
One exercise for every milestone, in the same order and wording
01
Milestone 01 · exact map
Structs, methods, and Go's type system
Source milestone

Structs and methods — Go's type system without classes or inheritance

Primary exercise
Define a Rectangle struct with Width and Height float64 fields. Add an Area() method and a Perimeter() method on the value receiver. Add a Scale(factor float64) method on a pointer receiver. Create a rectangle, call all three methods. Write a comment explaining why Scale needs a pointer receiver and Area doesn't.
Still stuck?

Tour of Go — 'Methods'. Go docs — 'Effective Go: Methods'.

02
Milestone 02 · exact map
Interfaces — implicit satisfaction
Source milestone

Interfaces — implicit satisfaction, how Go achieves polymorphism without explicit implements

Primary exercise
Write a Shape interface with Area() float64 and Perimeter() float64. Write a Circle and Rectangle type that both satisfy it without declaring 'implements Shape' anywhere. Store both in a []Shape slice and call Area() and Perimeter() through the interface. Then write a function that takes a Shape and prints both values — pass it both types.
Still stuck?

Tour of Go — 'Interfaces'. A Tour of Go — 'Type assertions'.

03
Milestone 03 · exact map
Error handling — explicit returns
Source milestone

Error handling — explicit error returns, the error interface, when to wrap vs return raw

Primary exercise
Write a divide(a, b float64) (float64, error) function that returns an error when b is zero. Call it twice: once with valid inputs and once with b=0. Handle both cases at the call site with if err != nil. Then write a function that wraps this error with fmt.Errorf('divide failed: %w', err). Unwrap it with errors.Is.
Still stuck?

Go blog — 'Error handling and Go'. Tour of Go — 'Errors'.

04
Milestone 04 · exact map
Goroutines — lightweight concurrency
Source milestone

Goroutines — what they are, how they differ from threads, the cost model

Primary exercise
Launch 5 goroutines that each sleep for a random duration and then print their index. Run the program and observe the non-deterministic print order. Add a sync.WaitGroup to wait for all of them to finish before the program exits. Without the WaitGroup, observe the program exiting before all goroutines complete.
Still stuck?

Tour of Go — 'Goroutines'. Go blog — 'Concurrency is not parallelism'.

05
Milestone 05 · exact map
Channels — communication between goroutines
Source milestone

Channels — how goroutines communicate, buffered vs unbuffered, select statement

Primary exercise
Write a producer goroutine that sends 10 integers to a channel, then closes it. Write a consumer that receives from the channel using range until it's closed. Run both. Then rewrite using a buffered channel of size 5 — observe that the producer can send 5 items without blocking before the consumer starts.
Still stuck?

Tour of Go — 'Channels' and 'Select'. Go blog — 'Go Concurrency Patterns'.

06
Milestone 06 · exact map
net/http — handlers and routing
Source milestone

The standard library net/http — writing an HTTP handler, registering routes, serving responses

Primary exercise
Write an HTTP server with three routes: GET /health returns 200 OK, GET /users returns a JSON array of hardcoded users, POST /users reads a JSON body and prints it. Use only the standard library. Test with curl. Write a comment on what http.Handler is and how http.HandleFunc satisfies it.
Still stuck?

Go docs — net/http package. Go blog — 'Writing Web Applications'.

07
Milestone 07 · exact map
JSON encoding and decoding
Source milestone

JSON encoding and decoding — struct tags, marshaling to and from JSON, handling optional fields

Primary exercise
Define a User struct with json struct tags. Marshal a User to JSON and print it. Then unmarshal a JSON string into a User and access its fields. Add an optional field using a pointer type (*string) and a json:",omitempty" tag — confirm it's omitted from the output when nil but included when set.
Still stuck?

Go docs — encoding/json package. Go blog — 'JSON and Go'.

08
Milestone 08 · exact map
Database connection — sql.DB and queries
Source milestone

Connecting to a database — sql.DB, prepared statements, scanning rows into structs

Primary exercise
Connect to a SQLite database using database/sql and the mattn/go-sqlite3 driver. Create a users table. Write functions to insert a user, query all users, and query a user by ID. Use prepared statements for the insert and ID lookup. Scan rows into User structs. Handle the case where no row is found (sql.ErrNoRows).
Still stuck?

Go docs — database/sql package. Go wiki — 'SQLDrivers'.

09
Milestone 09 · exact map
Middleware — wrapping handlers
Source milestone

Middleware — wrapping handlers for logging, auth, CORS

Primary exercise
Write a logging middleware that wraps any http.Handler: it records the method, path, and duration of every request. Write a CORS middleware that adds the appropriate headers to every response. Chain both middlewares around your user endpoints using a simple wrapper function pattern.
Still stuck?

Go blog — 'Making a RESTful JSON API in Go'. Search 'Go middleware pattern'.

10
Milestone 10 · exact map
Go modules — dependency management
Source milestone

Go modules — go.mod, importing packages, versioning

Primary exercise
Initialize a new module with go mod init. Add one external dependency (a router like chi or a logger like slog). Run go mod tidy. Inspect go.mod and go.sum — write a comment explaining what each file's job is. Then vendor the dependencies with go mod vendor and confirm the project builds with -mod=vendor.
Still stuck?

Go docs — 'Using Go Modules'. go.dev/blog/using-go-modules.

Hono / Hono JSX
Framework surface: routing, context, SSR JSX, middleware, bindings, and errors
01
Milestone 01 · exact map
Routing basics — handlers, params, and route groups
Source milestone

Routing basics — app.get/post/etc, path params, route grouping

Primary exercise
Build a small Hono app with a grouped /api route. Add GET /api/services, GET /api/services/:id, and POST /api/services. Parse the path parameter, return 404 for an unknown ID, and verify every route with curl.
Still stuck?

Hono docs — Routing and Route Grouping.

02
Milestone 02 · exact map
Context object — request and response helpers
Source milestone

Context object — c.req, c.res, c.json(), c.html(), what's attached to it per-request

Primary exercise
Write one handler that reads a query parameter, a request header, and a JSON body through c.req, then returns the normalized result with c.json(). Add another handler that returns an HTML diagnostic page with c.html().
Still stuck?

Hono docs — Context and Request.

03
Milestone 03 · exact map
Hono JSX — server-rendered HTML
Source milestone

Hono JSX — server-rendered by default, how a component returns HTML not a virtual DOM tree to hydrate

Primary exercise
Create a StatusPage JSX component that receives typed service data and renders semantic HTML. Open it in the browser, inspect View Source, and confirm the complete service list arrived as HTML without a client bundle.
Still stuck?

Hono docs — JSX and JSX Renderer.

04
Milestone 04 · exact map
Client components — explicit interactivity
Source milestone

Client components — the explicit opt-in mechanism, and when you actually need one vs. plain SSR

Primary exercise
Add one genuinely interactive control to the status page, such as a client-side incident filter or refresh button. Keep the service list server-rendered. Document why only that control needs client JavaScript.
Still stuck?

Hono docs — Client Components; progressive enhancement references.

05
Milestone 05 · exact map
Middleware — composition and next()
Source milestone

Middleware — what it is, built-in middleware (logger, cors), writing a small custom one

Primary exercise
Add Hono logger and CORS middleware, then write custom timing middleware that records method, path, status, and elapsed milliseconds. Use await next() correctly so the timer includes the downstream handler.
Still stuck?

Hono docs — Middleware and built-in middleware.

06
Milestone 06 · exact map
Environment bindings — typed platform access
Source milestone

Environment bindings — how env.DB, env.KV, etc. get typed and passed into a handler

Primary exercise
Define a Bindings type containing DB and STATUS_KV. Create a route that reads both bindings through c.env and returns a small diagnostic response. Intentionally misspell one binding and let TypeScript expose the mismatch.
Still stuck?

Hono docs — Cloudflare Workers adapter and Bindings typing.

07
Milestone 07 · exact map
Error handling — controlled failure responses
Source milestone

Error handling — app.onError, throwing HTTPException, what an unhandled error does on Workers

Primary exercise
Configure app.onError to return a stable JSON error shape. Add one route that throws HTTPException(404) and another that throws an ordinary Error. Verify status codes, response bodies, and that internal details are not leaked.
Still stuck?

Hono docs — Error Handling and HTTPException.

Cloudflare Workers
Runtime model, scheduled work, limits, environments, and deployment
01
Milestone 01 · exact map
Workers execution model — no durable module state
Source milestone

The Workers execution model — what's isolated per-request, what isn't, why you can't just hold state in a module-level variable and expect it to persist reliably

Primary exercise
Create a module-level counter and increment it in a fetch handler. Send repeated requests locally and after deploy, then document why any apparent persistence is an optimization rather than a storage guarantee. Replace it with a durable D1 or KV-backed value.
Still stuck?

Cloudflare Workers docs — Runtime APIs and How Workers works.

02
Milestone 02 · exact map
waitUntil — post-response work
Source milestone

waitUntil and the execution context — doing work after the response is sent without the worker being killed early

Primary exercise
Build a route that immediately returns 202 and schedules a simulated audit-log write with c.executionCtx.waitUntil(). Verify the response arrives before the background work finishes and that the write completes.
Still stuck?

Cloudflare Workers docs — ExecutionContext.waitUntil().

03
Milestone 03 · exact map
Scheduled events — cron-driven polling
Source milestone

Scheduled events — the scheduled() export, how cron syntax maps to trigger frequency in wrangler config

Primary exercise
Add a scheduled handler and a cron trigger in wrangler configuration. In local development, trigger it manually and have it poll one known endpoint, logging normalized status, latency, and timestamp.
Still stuck?

Cloudflare Workers docs — Cron Triggers and local testing.

04
Milestone 04 · exact map
CPU time limits — synchronous work budget
Source milestone

CPU time limits — what counts against it, why an infinite loop or heavy synchronous work is dangerous here specifically

Primary exercise
Write a benchmark route that performs increasing amounts of synchronous computation and record CPU time in local/preview logs. Refactor the task into smaller or more appropriate work and document what waiting on fetch does versus CPU computation.
Still stuck?

Cloudflare Workers docs — Limits and CPU time.

05
Milestone 05 · exact map
Subrequest limits — bounded fan-out
Source milestone

Subrequest limits — how many outbound fetches a single invocation can make, and why that caps your fan-out design directly

Primary exercise
Implement a poller that fans out to a configurable list of mock services. Count outbound fetches explicitly and reject or batch a configuration that would exceed your chosen per-invocation budget.
Still stuck?

Cloudflare Workers docs — Subrequest limits.

06
Milestone 06 · exact map
Cold starts — startup cost awareness
Source milestone

Cold starts — what makes Workers fast to cold-start relative to a traditional server, and what still costs time

Primary exercise
Measure the first and subsequent request latency for a lightweight Worker route. Then add expensive module-scope initialization and measure again. Move avoidable work out of startup and compare.
Still stuck?

Cloudflare Workers docs — performance and startup guidance.

07
Milestone 07 · exact map
Wrangler environments, secrets, and deployment
Source milestone

wrangler dev vs wrangler deploy — local dev loop, environments, secrets

Primary exercise
Create development and production Wrangler environments with separate D1/KV bindings. Add a secret locally and in the deployed environment, then write a diagnostic route that confirms presence without exposing the value.
Still stuck?

Cloudflare Workers docs — Wrangler environments, bindings, and secrets.

Cloudflare D1
Relational ownership of services, history, incidents, and queries
01
Milestone 01 · exact map
Schema design — services, history, incidents
Source milestone

Schema design for this project — services, incidents, status_history tables and their relationships

Primary exercise
Design and create services, status_history, and incidents tables with primary keys, foreign keys, timestamps, and indexes that support the status-page queries. Draw the relationships before writing SQL.
Still stuck?

Cloudflare D1 docs — SQL API; SQLite foreign keys and indexes.

02
Milestone 02 · exact map
Migrations — reproducible schema evolution
Source milestone

Migrations — wrangler d1 migrations, keeping schema changes versioned and repeatable

Primary exercise
Create an initial migration, apply it locally, seed data, then add a second migration that introduces an enabled flag or latency column. Rebuild a fresh local database using only migrations and verify the schema matches.
Still stuck?

Cloudflare D1 docs — Migrations.

03
Milestone 03 · exact map
D1 binding API — prepare, bind, run, all, first
Source milestone

The D1 binding API — prepare(), bind(), .run(), .all(), .first(), and when to use each

Primary exercise
Implement repository functions for createService, findServiceById, listServices, and updateServiceState. Use prepare/bind and deliberately choose run, first, or all according to the expected result.
Still stuck?

Cloudflare D1 docs — Prepared statements and return methods.

04
Milestone 04 · exact map
Batch operations — one poll cycle
Source milestone

Batch operations — D1's batch() for multiple statements, and why it matters for write-heavy operations like recording a poll cycle

Primary exercise
Record one poll cycle containing several status_history inserts through DB.batch(). Compare the implementation and timing with sequential awaits, and decide what should happen if one statement fails.
Still stuck?

Cloudflare D1 docs — batch statements.

05
Milestone 05 · exact map
Domain queries — current status and incidents
Source milestone

Query patterns for this domain — get current status per service, get incident history for a service, get all incidents in a date range

Primary exercise
Write and verify three queries: latest status per service, incident history for one service, and incidents overlapping a date range. Seed edge-case data so ties and services with no history are handled deliberately.
Still stuck?

D1/SQLite docs — window functions, joins, and query plans.

06
Milestone 06 · exact map
Consistency, transactions, and retention
Source milestone

D1's consistency and transaction model — what you can and can't rely on relative to a traditional RDBMS

Primary exercise
Write a short decision note defining which uptime-checker operations require atomicity, what D1 guarantees you rely on, and how the application handles duplicate scheduled invocations or partial failures. Implement an idempotency key for a poll cycle.
Still stuck?

Cloudflare D1 docs — transactions, consistency, and limits.

Workers KV
Fast current-status snapshots with explicit staleness behavior
01
Milestone 01 · exact map
KV binding API — snapshot CRUD
Source milestone

The KV binding API — get(), put(), delete(), list(), and JSON serialization patterns

Primary exercise
Write a complete current-status snapshot to one KV key as JSON, read it back with type validation, list versioned snapshot keys, and delete a test key. Keep serialization in one adapter rather than spreading JSON.parse/stringify across routes.
Still stuck?

Workers KV docs — get, put, list, delete, and metadata.

02
Milestone 02 · exact map
Eventual consistency — acceptable stale reads
Source milestone

Eventual consistency — what it means concretely for a value written by a cron job and read by a status page

Primary exercise
Add generatedAt and pollCycleId fields to the snapshot. After a write, have the status route report the age and cycle ID it observed. Document why a briefly stale read is acceptable here and where it would not be.
Still stuck?

Workers KV docs — consistency model.

03
Milestone 03 · exact map
TTL and expiration — stale-data policy
Source milestone

TTLs and expiration — whether the status snapshot should expire, and what happens on read if it does

Primary exercise
Choose and implement a snapshot expiration policy. When the key is missing or expired, return a clear no-current-data state or fall back to a D1 latest-status query. Test both paths.
Still stuck?

Workers KV docs — expiration and TTL.

04
Milestone 04 · exact map
KV versus D1 — explicit ownership boundary
Source milestone

KV vs D1 for this exact project — articulating the boundary in your own words, not just reciting the docs

Primary exercise
Write a one-page storage decision table for services, status_history, incidents, and current snapshot. Then implement a route that proves the boundary: configuration/history comes from D1; the public current view comes from KV with a D1 fallback.
Still stuck?

Cloudflare storage docs — choosing D1 versus KV.

05
Milestone 05 · exact map
Read/write shape — one snapshot per cycle
Source milestone

Read/write limit shape — why KV is built for high-read/low-write and how that matches (or doesn't) each piece of this project's data

Primary exercise
Instrument snapshot reads and writes. Verify one poll cycle performs one KV write while many status-page requests perform reads. Add a guard preventing a write per individual service check.
Still stuck?

Workers KV docs — limits and pricing model.

Vitest for Workers
Runtime-faithful tests for bindings, scheduled handlers, and failure paths
01
Milestone 01 · exact map
vitest-pool-workers — workerd test runtime
Source milestone

vitest-pool-workers setup — the config that runs your tests inside workerd, not a Node shim

Primary exercise
Configure @cloudflare/vitest-pool-workers and write a smoke test against the Hono app. Prove the test is running in the Workers runtime by checking a Workers API and confirming an unavailable Node-only assumption fails.
Still stuck?

Cloudflare Workers docs — Vitest integration.

02
Milestone 02 · exact map
D1 test isolation — seeded relational state
Source milestone

Mocking or seeding D1 in tests — using a test binding, resetting state between tests

Primary exercise
Create a helper that applies migrations and seeds service/history rows for each test. Write two tests whose data would conflict if isolation were broken, and verify each starts with the expected clean state.
Still stuck?

Workers Vitest D1 documentation and test isolation guidance.

03
Milestone 03 · exact map
KV test isolation — deterministic snapshots
Source milestone

Mocking or seeding KV in tests — same idea, isolated per test

Primary exercise
Seed a current-status snapshot in the test KV binding, read it through the real adapter, then delete or replace it. Write a second test proving the previous snapshot does not leak into its state.
Still stuck?

Workers Vitest KV documentation.

04
Milestone 04 · exact map
Scheduled handler tests — assert side effects
Source milestone

Calling scheduled() directly in a test — constructing a fake ScheduledEvent and asserting on side effects

Primary exercise
Invoke the exported scheduled handler in a test with seeded D1 services and mocked outbound fetches. Await the execution context, then assert history rows, incident transitions, and the KV snapshot.
Still stuck?

Cloudflare Vitest docs — scheduled events and execution contexts.

05
Milestone 05 · exact map
Failure-path tests — partial success and resilience
Source milestone

Testing failure paths — a service that times out, a D1 write that fails, a KV read that returns null

Primary exercise
Write separate tests for a timed-out service, a rejected fetch, a missing KV snapshot, and a simulated storage failure. Verify one failing service does not prevent other results from being recorded and surfaced.
Still stuck?

Vitest docs — mocking, fake timers, and Workers testing guidance.