Collections, Generics, and Midterm Review
How do we organize the knowledge of the church?
Chapter 8 — Collections, Generics, and Midterm Review
“But all things should be done decently and in order.” — 1 Corinthians 14:40
“The standard library is the senior engineer’s longest-standing friend. Learn it like family.” — Joshua Bloch, Effective Java (adapted)
Why This Matters
This is the two-part chapter that closes Phase 1.
Part A teaches the part of Java’s standard library that working programmers actually use every day: Map, Set, List, and the generics that hold them together. You have been using ArrayList<String> since Chapter 13 of Coding 1 (Chapter 7 accelerated). You have used HashMap<Integer, Long> in Chapter 7. You probably haven’t asked, formally, why those specific types? — when would you reach for LinkedHashMap instead, or TreeMap, or HashSet, or LinkedList? — and what does <String> actually mean to the compiler? Part A answers those questions. The judgments are the deliverable. You will use them in the midterm; you will use them in every Java program you write for the rest of your life.
Part B is the midterm review. Seven chapters of Phase 1 compressed into one re-readable reference. The cumulative bug list. The patterns that combine across chapters. The seven-day study plan with a compressed three-day fallback for students who started preparing late.
Project 8 — the Apologetics Catalog — is the midterm itself. Sixty minutes, closed AI, closed internet, open textbook. The same shape as Coding 1’s midterm: a small real catalog system built from the cumulative Phase 1 skills, with the apologetics framing replacing the mission-trip framing of Coding 1. The catalog you build is small. The point is that you can build it under exam conditions, from your own hands, with seven chapters of training cashed in.
Part A — Collections and Generics
8.1 — Three Interfaces You’ll Use Forever
Java’s java.util package gives you three top-level collection interfaces. Almost every collection you’ll use implements one of them.
| Interface | What it represents | When you reach for it |
|---|---|---|
List<E> | Ordered sequence, allows duplicates | ”I have a sequence of things in a known order.” |
Set<E> | Unordered collection, no duplicates | ”I need to know whether a thing has been seen.” |
Map<K, V> | Key-to-value lookup table | ”I have a name, and I want to find the thing it names.” |
Behind each interface are multiple implementations, each with different tradeoffs. Senior engineers pick the implementation that fits the access pattern. Most students pick whatever they typed first and hope. We are going to fix that.
Coach’s Note — The rule of thumb: program against the interface, instantiate the implementation.
List<String> roster = new ArrayList<>();notArrayList<String> roster = new ArrayList<>();. The first version makes your callers and parameters happy and lets you swap toLinkedListlater if you need. The second version locks you in.
8.2 — List: ArrayList vs LinkedList
Two implementations, two different shapes.
ArrayList<E>
Backed by a resizable array. Random access (get(i)) is O(1) — constant time. Insert at the end (add(x)) is O(1) amortized. Insert in the middle is O(n) because everything after the insertion point has to shift.
This is the default List you should reach for. 95% of the time, this is the right answer.
List<String> roster = new ArrayList<>();
roster.add("Maya");
roster.add("Marcus");
roster.add("Lin");
String first = roster.get(0); // O(1)
int size = roster.size(); // O(1)
boolean has = roster.contains("Maya"); // O(n) — must scan
roster.remove("Maya"); // O(n) — scan + shift
LinkedList<E>
Backed by a doubly-linked list of nodes. Each node holds a value and pointers to its neighbors. Random access (get(i)) is O(n) — you walk the chain. Insert/remove at either end or via an iterator’s current position is O(1).
List<String> queue = new LinkedList<>();
queue.add("first");
queue.add("second");
queue.add("third");
queue.removeFirst(); // O(1) — just unhook the head
When to use which?
ArrayListfor almost everything. Sequential access is fast; random access is fast. The cache locality of the underlying array is so good that even insertions in the middle are often faster thanLinkedListin practice for small-to-medium lists.LinkedListwhen you genuinely need a queue or deque (useArrayDequefor that; it’s usually faster). Or when you’ll be inserting/removing in the middle frequently and iterating sequentially.
Honestly, in 15 years of professional Java, the times I’ve reached for LinkedList deliberately can be counted on two hands. The Coding 3 chapter on data structures will explain why both exist, and what’s actually happening under each. For Coding 2: default to ArrayList.
8.3 — Set: HashSet vs LinkedHashSet vs TreeSet
A Set holds distinct values. Adding a duplicate is a no-op.
HashSet<E> — fast, unordered
Backed by a hash table. add, contains, remove are all O(1) average. Iteration order is undefined — the elements come out in whatever order the hash table happens to lay them out in. Do not depend on the order.
Set<String> authors = new HashSet<>();
authors.add("Lewis");
authors.add("Chesterton");
authors.add("Lewis"); // no-op, duplicate
System.out.println(authors.size()); // 2
System.out.println(authors.contains("Lewis")); // true
LinkedHashSet<E> — fast, insertion-ordered
Same operations, but the iteration order is the order in which elements were added.
Set<String> visitedInOrder = new LinkedHashSet<>();
visitedInOrder.add("Genesis");
visitedInOrder.add("Exodus");
visitedInOrder.add("Genesis"); // no-op
for (String s : visitedInOrder) {
System.out.println(s); // Genesis, then Exodus
}
Reach for LinkedHashSet when you want deduplication and predictable iteration order. The performance cost over HashSet is small.
TreeSet<E> — sorted
Backed by a balanced binary tree. add, contains, remove are O(log n). Iteration order is the natural ordering of the elements (or whatever Comparator you supply).
Set<String> sortedAuthors = new TreeSet<>();
sortedAuthors.add("Lewis");
sortedAuthors.add("Chesterton");
sortedAuthors.add("Augustine");
for (String s : sortedAuthors) {
System.out.println(s); // Augustine, Chesterton, Lewis
}
TreeSet is slower per operation than HashSet, but you get sorted iteration for free.
When to use which?
HashSetfor “have I seen this?” with no order needed.LinkedHashSetwhen iteration order matters and matches insertion order.TreeSetwhen you need sorted iteration (e.g., printing all unique authors alphabetically).
Coach’s Note —
HashSetis the default. The rule: pickHashSetunless you specifically need order. If you need order, pick the cheapest one that gives you the order you want —LinkedHashSetfor insertion order,TreeSetfor sorted order.
8.4 — Map: HashMap vs LinkedHashMap vs TreeMap
The same three flavors, applied to key-value pairs.
HashMap<K, V> — fast, unordered
Map<String, Integer> wordCount = new HashMap<>();
wordCount.put("grace", 3);
wordCount.put("peace", 2);
wordCount.put("grace", 4); // overwrites
System.out.println(wordCount.get("grace")); // 4
System.out.println(wordCount.get("hope")); // null
System.out.println(wordCount.containsKey("hope")); // false
Same shape: O(1) average for put, get, containsKey. Iteration order undefined.
LinkedHashMap<K, V> — fast, insertion-ordered
Iteration over entries returns them in insertion order. Otherwise identical to HashMap.
TreeMap<K, V> — sorted by key
Backed by a balanced tree. Iteration in key-sorted order. O(log n) per operation.
Map<String, Integer> sortedCount = new TreeMap<>();
sortedCount.put("zeal", 1);
sortedCount.put("grace", 3);
sortedCount.put("hope", 2);
for (var entry : sortedCount.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
// grace = 3, hope = 2, zeal = 1
}
When to use which?
HashMapfor the default lookup table.LinkedHashMapwhen you want to preserve the insertion order of entries — for example, when reading a config file and wanting to write it back out in the same order.TreeMapwhen you need keys in sorted order — for example, a price list rendered alphabetically, or a date-keyed log iterated chronologically.
8.5 — The Most-Used Methods
Across all three families, the methods you’ll use over and over:
List<String> list = new ArrayList<>();
list.add("x");
list.get(0);
list.size();
list.isEmpty();
list.contains("x");
list.remove("x");
list.indexOf("x");
list.set(0, "y");
for (String s : list) { ... }
Set<String> set = new HashSet<>();
set.add("x");
set.size();
set.contains("x");
set.remove("x");
for (String s : set) { ... }
Map<String, Integer> map = new HashMap<>();
map.put("x", 1);
map.get("x"); // returns null if absent
map.getOrDefault("x", 0); // returns default if absent
map.containsKey("x");
map.size();
map.remove("x");
for (var entry : map.entrySet()) { ... }
for (String key : map.keySet()) { ... }
for (Integer val : map.values()) { ... }
getOrDefault is especially handy. It saves you the if (map.containsKey(k)) ... else ... boilerplate.
// Increment a counter without ceremony
map.put(key, map.getOrDefault(key, 0) + 1);
8.6 — Iteration That Doesn’t Throw
In Chapter 5 we met ConcurrentModificationException — what you get if you modify a collection while iterating it. The fix:
Option 1 — collect-then-modify:
List<String> toRemove = new ArrayList<>();
for (String s : roster) {
if (s.startsWith("X")) toRemove.add(s);
}
roster.removeAll(toRemove);
Option 2 — explicit iterator with iterator.remove():
Iterator<String> it = roster.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.startsWith("X")) it.remove(); // OK — uses the iterator
}
Option 3 — removeIf (Java 8+):
roster.removeIf(s -> s.startsWith("X"));
The third is the modern way and what working Java developers reach for. The lambda s -> s.startsWith("X") is a tiny inline predicate. For your purposes in Coding 2, treat removeIf as “remove every element that satisfies the predicate.”
8.7 — Generics: What <T> Actually Means
You have been writing ArrayList<String> for chapters. The <String> part is a generic type parameter — you are telling the compiler, “this list holds Strings, and only Strings.”
Before generics (Java 1.4 and earlier), collections held Object, and every read required a cast:
// Bad old days
ArrayList list = new ArrayList(); // ArrayList of anything
list.add("hello");
String s = (String) list.get(0); // explicit cast required
// If you accidentally added an Integer, the cast fails at runtime
With generics:
ArrayList<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // no cast — compiler knows
list.add(42); // compile error — type mismatch
The compiler enforces the type at compile time. By the time the program runs, the cast has been inserted automatically and is guaranteed to succeed. This is one of Java’s better safety features.
Writing your own generic methods
You can write a method with its own type parameter:
public static <T> T firstOrNull(List<T> items) {
if (items.isEmpty()) return null;
return items.get(0);
}
The <T> before the return type declares T as a type parameter for this method. The compiler infers the actual type from the argument:
List<String> names = List.of("Maya", "Marcus");
String first = firstOrNull(names); // T inferred as String
List<Integer> scores = List.of(90, 85);
Integer top = firstOrNull(scores); // T inferred as Integer
Writing generic classes
public class Box<T> {
private T contents;
public Box(T initial) { this.contents = initial; }
public T get() { return contents; }
public void set(T value) { this.contents = value; }
}
Box<String> b = new Box<>("Hello");
String s = b.get();
<T> after the class name declares the type parameter. The constructor and methods can use T as if it were a real type.
Bounded generics
Sometimes you need T to be at least a certain type:
public static <T extends Number> double sum(List<T> items) {
double total = 0;
for (T item : items) {
total += item.doubleValue(); // OK — Number has doubleValue()
}
return total;
}
<T extends Number> says “T is some subtype of Number.” Inside the method you can call any Number method on T values. The compiler enforces that you only call sum with a list of numbers.
For Coding 2, bounded generics show up most often as <T extends Comparable<T>> for “T must be comparable to itself,” which you’ll use any time you want to sort a generic list.
Coach’s Note — Generics are how Java keeps you honest about types without forcing you to write the same method N times for N types. They are also one of the most consistently-misunderstood corners of the language. The rules above cover 90% of what you’ll use. The other 10% (wildcards
<?>, PECS, type erasure quirks) is a Coding 3 topic — you don’t need it now.
8.8 — Foreshadowing: We Use These; Coding 3 Shows You What’s Under
This week you used HashMap, TreeSet, ArrayList as black boxes. They worked.
Coding 3 will open the boxes. You will:
- Implement a hash table from scratch — including hash functions, collision resolution, and resizing.
- Implement a binary search tree by hand, then balance it (AVL or red-black).
- Implement a linked list, including the doubly-linked variant.
- Measure their performance with real big-O analysis, not just rules of thumb.
The reason we use them as black boxes this term is that that’s how working programmers actually use them. You spend years using HashMap before you ever implement one. When the time comes, the implementation is fascinating — but using them well is more economically important and has to come first. The order matters.
You also got a small taste of what the implementations are in this chapter:
ArrayListis “a resizable array.”LinkedListis “a doubly-linked chain of nodes.”HashMapis “a hash table.”TreeMapis “a balanced binary tree.”
That much description should be enough to develop intuition for the tradeoffs without writing a line of structure code yet. Coding 3 fills in the rest.
Part B — Midterm Review
8.9 — The Seven Chapters of Phase 1, Compressed
A one-paragraph reminder of each. If any feels unfamiliar, re-read it.
Chapter 1 — Reading Code Like Scripture. The senior engineer reads more code than they write. Reading is the primary skill AI will not give you. The chapter introduced disciplined reading: read top-down for shape, bottom-up for detail, and aloud when stuck. You wrote the Code Comprehension Brief — a one-page summary of an unfamiliar program — and that exercise is the model for every code review you’ll do for the rest of your career.
Chapter 2 — Contracts and Specifications. A spec is a promise about behavior. Pre/postconditions, invariants, signatures-as-contracts, Javadoc. Writing the spec before the code disciplines your thinking and produces something you can hand to a reviewer (or, in Phase 2, to an AI). The covenant metaphor is real — a promise made and kept.
Chapter 3 — Exception Handling. try, catch, finally, throws. Checked vs unchecked. Custom exception classes. Exception chaining. The lesson: programs handle errors deliberately or accidentally. Deliberately is professional. Accidentally is what you ship when you don’t think about errors at all.
Chapter 4 — Testing as Discipline. JUnit 5. Arrange/Act/Assert. Test-driven development — write the test first, watch it fail, write the smallest code that makes it pass, refactor. The unit/integration distinction. Trust is the deliverable — your tests are how you (and a future reader, including an AI) know the code is correct.
Chapter 5 — Debugging Discipline. Stack traces, hypothesis-driven debugging, bisection. println vs the debugger. When to give up and rewrite. The five-run constraint of Project 5 trains the most important reflex: form the hypothesis before you change a line. Regression tests lock the fix in place.
Chapter 6 — Files, Data, and Persistence. java.nio.file (readString, writeString, Path.of), careful CSV parsing, hand-rolled JSON writing, validation at the door (records with compact constructors), and the atomic-write pattern (write-then-rename) to never corrupt a file. Data stewardship as a craft.
Chapter 7 — Recursion. Base case + recursive case. Stack frames. Mathematical recursion (gcd, factorial, Fibonacci) and structural recursion (nested map, directory walker). Memoization. When iteration is clearer. The seed of trees and graphs for Coding 3.
That’s Phase 1.
8.10 — The Patterns That Repeat
Across the seven chapters, certain compositional patterns reappear. The midterm will combine them. Learn the combinations, not just the parts.
Pattern A — Spec, Test, Implement, Refactor
The senior-engineer rhythm from Chapters 2 and 4:
- Write the Javadoc spec for the class or method.
- Write a failing test that exercises the spec.
- Write the smallest code that makes the test pass.
- Refactor for clarity without changing behavior; tests confirm.
You will use this rhythm on the midterm if you want full credit. Even under a 60-minute clock, write at least one test before the code it tests. The discipline pays for itself in time saved on debugging.
Pattern B — Validate at the Door
Records with compact constructors (Chapter 6) and exception-throwing setters (Chapter 3):
public record Resource(String title, String author, int year) {
public Resource {
if (title == null || title.isBlank()) throw new IllegalArgumentException("title");
if (author == null || author.isBlank()) throw new IllegalArgumentException("author");
if (year < 0) throw new IllegalArgumentException("year must be non-negative");
}
}
Invalid objects cannot exist. Every code path that produces a Resource is covered. This is the strongest layer of defensive coding.
Pattern C — Read, Validate, Transform, Write (Atomically)
The data pipeline from Chapter 6:
List<Resource> entries = readCsv(in);
validate(entries);
List<Resource> cleaned = normalize(entries);
String json = JsonOut.writeAll(cleaned);
SafeWrite.writeAtomic(out, json);
Each step is small. Each step is testable. The atomic write at the end protects the previous good copy. This is the pattern for any program that reads and writes data.
Pattern D — Hypothesis-Probe-Fix-Test
The debugging rhythm from Chapter 5:
- Observe the symptom precisely.
- Write the hypothesis as a comment.
- Probe with the smallest experiment that distinguishes the hypothesis from rivals.
- Confirm or revise. Do not change code until confirmed.
- Fix. Add a regression test. Verify.
The rhythm works on your own code, on classmates’ code, and (Phase 2) on AI-generated code. Same shape every time.
Pattern E — Recurse on Structure
The pattern from Chapter 7:
- Base case: smallest input handled directly.
- Recursive case: combine the answer from a smaller version of the same problem.
Whenever the data is nested (directories, configs, expressions, trees that haven’t been named that yet), the function shape that walks it is recursive. Don’t fight it with a loop; let the function shape match the data shape.
Pattern F — Pick the Right Collection
The judgment from §8.2–8.4:
- Lookup by name?
Map. - Distinct membership?
Set. - Ordered sequence?
List. - Need iteration order?
LinkedHashX. - Need sorted order?
TreeX. - Otherwise?
HashXorArrayList.
These six lines, internalized, will steer you to the right type 90% of the time.
Coach’s Note — If you can write Patterns A + B + C in combination, from scratch, in 30 minutes — read a CSV, validate every entry, write the cleaned JSON, with one passing test — you are ready for the midterm. That is the entire engine.
8.11 — The Cumulative Bug List
The bugs you’re most likely to hit on the midterm — and on every project after it. Read this list the morning of the exam. Tape it to your wall.
==vs.equals()for objects.Strings, records, custom classes — always.equals().==for primitives only.NullPointerExceptionfrom a nested expression.map.get(k).getName()NPEs whenmap.get(k)returns null. Decompose the line; read the helpful NPE message.IndexOutOfBoundsException/ArrayIndexOutOfBoundsException. Loop bound<=when it should be<. Or accessinglist.get(list.size()).NumberFormatException. A field you parsed withInteger.parseInthad whitespace, a BOM, or wasn’t numeric..trim()first; validate.ConcurrentModificationException. Modifying a collection during iteration. UseremoveIf, collect-then-modify, oriterator.remove().NoSuchFileExceptionon a “right there” file. Relative path resolved against an unexpected working directory. PrintPath.of(".").toAbsolutePath()to see where the JVM thinks it is.- Truncated file on write.
Files.writeStringoverwrites. If you meant to append, useStandardOpenOption.APPEND. If you meant to never corrupt the previous good copy, use the atomic-write pattern. HashMapiteration in the “wrong” order. Iteration order ofHashMapandHashSetis undefined. If order matters, useLinkedHashMap/LinkedHashSetorTreeMap/TreeSet.StackOverflowErrorfrom recursion. Missing base case or non-shrinking recursive call. Trace by hand from the smallest input.- Off-by-one in any loop or recursion. Did the loop start at 0 or 1? Did it end at
< nor<= n? Trace with a tiny input. - Integer division when you meant double.
total / counttruncates if both are int. Cast or use1.0 *to coerce. - Forgot
@Override. If a method should override and doesn’t, the compiler is silent without the annotation. With@Override, the compiler catches typos in the signature. - Test that “passes” because it doesn’t actually assert. Reading test code: confirm every test ends with an
assertX(...)call. A test with no assertion is a “test” that always passes. - Modified shared state across recursive calls and got tangled results. Pass state as a parameter; don’t share via a mutable static field.
- Exception caught and silently swallowed.
catch (Exception e) {}is almost always a bug. At least log; usually rethrow.
Fifteen items. Read all fifteen the morning of the exam. They are the most common sources of point loss on the midterm.
8.12 — How to Study This Week
You have one week. Spend it like this.
Day 1 — Re-do the warm-up reps
Open the exercises in each of Chapters 1–7. Pick 2–3 reps per chapter that you found hardest the first time. Do them again. From memory.
You will be surprised what has cemented and what hasn’t.
Day 2 — Combine
The combination drills in this chapter’s the exercises are short problems that mix concepts. Each one takes 15–30 minutes. They are deliberately not “one chapter at a time” — they are “chapter 2 spec + chapter 4 test + chapter 6 read + chapter 8 collection.” Doing them under a loose timer is the closest you’ll come to the midterm before the midterm.
Day 3 — One full sample midterm
Open the sample midterm prompt (instructions are at the top of the file). Read the prompt. Close the prompt. Open a fresh project. Solve from scratch in 60 minutes.
Then open the sample solution (Hymnal.java, Hymn.java, SimpleCsv.java, HymnalSelfCheck.java) and compare. Notice where your code differs.
Day 4 — Review your own past work
Re-read your Projects 1–7. Especially the reflection comment blocks. The patterns you understood then are the patterns you still know. The patterns you struggled with — re-do the relevant reps.
Day 5 — Mock midterm #2
A different sample (one you build for yourself, or trade with a classmate). 60-minute timer. Closed AI. Closed internet. Just the textbook.
Day 6 — Diagnostic and gap-fill
Look at where the two mock midterms diverged from clean. Spend the day on whichever patterns failed. Watch out for the temptation to “review everything” — that produces a thin layer of nothing. Pick the two weakest patterns and drill them.
Day 7 (exam day) — Light review only
Re-read §8.11 (the bug list). Skim §8.10 (the patterns). Eat. Show up early.
Do not learn anything new the day of the exam. Cramming new material is self-harm. You either know it or you don’t; the cram only makes you anxious. Trust the reps.
Compressed 3-day fallback
You started preparing late. It happens. The cut-down version that still works:
- Day 1: Re-do one hard rep per chapter (1–7). Read §8.10 patterns once. Read §8.11 bugs twice.
- Day 2: Do the combination drills in the exercises end-to-end. Skip nothing.
- Day 3 (exam day): Re-read §8.11 bugs. Skim §8.10. Eat. Show up.
The compressed plan covers Normal tier comfortably and gets you partial credit on Medium. Hard tier under the compressed plan is unlikely — be honest about scope.
8.13 — Exam Rules and Logistics
The midterm — Project 8: Apologetics Catalog — is closed-book, closed-internet, closed-AI.
You may use:
- The textbook itself (paper or non-interactive PDF — no clickable links, no chat assistant).
- A printed copy of your own Chapter 1–7 exercises and projects, if you brought them.
- The compiler, JUnit, and your editor’s non-AI features (syntax highlighting, basic autocomplete, error squiggles).
You may not use:
- The internet.
- ChatGPT, Claude, Copilot, Cursor, JetBrains AI Assistant, any other AI.
- A friend’s code, in person or via messages.
- Notes that aren’t your own work from this course.
Time: 60 minutes (some institutions give 75 — the instructor will announce the exact time). Submit your project as an OnlineGDB link or a GitHub repo.
Grading: Normal completion = passing. Medium and Hard features = extra credit on top. The full rubric is in Project 8.
8.14 — A Direct Word on AI for the Midterm
This is the first test where you cannot reach for AI. You will probably feel the absence in the first ten minutes. Hold the discomfort. The discomfort is the test working.
If you have prepared honestly — typed the reps, written the projects, debugged your own bugs — the discomfort will pass. Your hands will start working. By minute twenty you will be writing code, slowly but recognizably.
If you have not prepared honestly — if the AI has been the keyboard for the last seven weeks — the discomfort will not pass, because there is no muscle memory underneath it. The midterm will tell you the truth.
That is not a punishment. It is information. You have eight more weeks to fix it before the final. Use the midterm score, whatever it is, as a diagnostic. The students whose midterm and final scores are both honest tend to graduate from this course with a skill they actually own.
Coach’s Note — I have watched students score lowest on the midterm and highest on the final. Without exception, those students used the midterm result honestly to recalibrate. They went back, re-did the reps, kept the AI off during personal practice, and gradually built the skill in their own hands. Do not fear the midterm. Use it.
8.15 — What’s On the Midterm (in General Shape)
The actual prompt is sealed until exam time. But the shape is no secret. The midterm will require you to:
- Define a small record or class to model a domain entity.
- Read entries from a file (CSV provided).
- Validate them — either at the door (compact constructor) or in a parse step.
- Store them in appropriate collections (
Map<K, V>for lookup,Set<E>for uniqueness,List<E>for sequence). - Provide a small API — lookup methods, list-all methods, filter methods.
- Persist changes back to file when needed.
- Be robust to malformed input — exception handling, useful error messages.
- Ship with at least one JUnit test for full Normal credit.
Sound familiar? It’s the Catechism Data Pipeline from Project 6, with the twist that the user interacts with the catalog via method calls (and on Hard tier, a CLI). The Project 8 spec describes it in detail — Normal, Medium, Hard. The prompt you’ll see in the exam room is a specific instance of that shape.
Read Project 8 now. Then come back here for the final coach’s note.
8.16 — Coach’s Final Word for Week 8
Half the course is behind you.
You have learned to read code carefully, write specifications before code, handle errors deliberately, test for trust, debug by hypothesis, steward data carefully, think recursively when the problem demands it, and reach for the right collection without thinking. Every program you ever write in any language for the rest of your career will draw on those seven skills.
Chapter 9 starts Phase 2 — partnership with AI. From here forward, the AI is part of your workflow. You will direct it. You will review its work. You will keep a log of every prompt you sent, and the grader will read it alongside your code. Your job becomes the senior engineer’s job: shape the system, vet the output, ship code you can stand behind.
But that is the back half.
The front half — the sharpening — is what gets tested next week. Take the midterm seriously. Show up rested. Trust your training. Submit honest work.
See you in the exam room. Chapter 9 starts the day after.
Up next: Read the exercises for combination drills, Project 8 for the midterm spec, and the sample midterm prompt for a timed practice prompt. Then Chapter 9 — Pair Programming With AI — once the midterm is behind you.