diff --git a/dynamic-programming/notes.md b/dynamic-programming/notes.md
index 3a21112746d655eca19b30a36552b35a74890dc9..c2821f3c1c89f1164bbce957e628bb7004047549 100644
--- a/dynamic-programming/notes.md
+++ b/dynamic-programming/notes.md
@@ -54,35 +54,35 @@ Problem: Subject to an integer weight limit, what choice of items (allowing repe
 ## DAG
 
 *   Edges (actions):
-    *   … or
-    *   …
+    *   Take one of the items or
+    *   Take one kg of nothing.
 *   Vertices (situations):
-    *   …
+    *   How much weight have we put into the backpack so far?
 *   Edge weights:
-    *   …
+    *   The value of the item being taken
 *   Topological order:
-    *   …
+    *   Increasing order : 0 → 1 → … → `weightLimit` (assuming that no items have nonpositive weight)
 *   Goal:
-    *   …
+    *   Find a longest path from 0 to `weightLimit`
 
 ## Backpointer Class
 
 *   Information for the final result:
-    *   …
+    *   What item did we take?
 *   Information to go back:
-    *   …
+    *   What was the weight of the item taken?  (this is redundant with the item taken)
 *   Information to compare quality:
-    *   …
+    *   What is the *total* value of all of the items taken so far?
 
 ## Choosing a Backpointer
 
 *   Exhaustive search:
-    *   Generate ….
-    *   Check that ….
+    *   Generate items from the list or else 1 kg of nothing, but make sure that we don't try to go back to a negative total weight.
+    *   Check that the item maximizes the total value (the previous total value plus the value of the item used to go back).
 
 ## Example
 
-*   Item Z ….
+*   Item Z weighs 1 kg and is worth $0.
 *   Item A weighs 3 kg and is worth $10.
 *   Item B weighs 4 kg and is worth $14.
 
@@ -91,20 +91,20 @@ Problem: Subject to an integer weight limit, what choice of items (allowing repe
     Weight (kg)   Item   Total Value  (Back to Weight)
     -----------  ------  -----------  ----------------
               0  [none]           $0  ⊥
-              1  Item …            …  …
-              2  Item …            …  …
-              3  Item …            …  …
-              4  Item …            …  …
-              5  Item …            …  …
-              6  Item …            …  …
-              7  Item …            …  …
-              8  Item …            …  …
-              9  Item …            …  …
-             10  Item …            …  …
+              1  Item Z            0  0
+              2  Item Z            0  1
+              3  Item A           10  0
+              4  Item B           14  0
+              5  Item Z           14  4
+              6  Item A           20  3
+              7  Item A           24  4
+              8  Item B           28  4
+              9  Item A           30  6
+             10  Item A           34  7
 
     Reversed Path
     ----
-    10 ← (Item …) ← …
+    10 ← (Item A) ← 7 ← (Item A) ← 4 ← (Item B) ← 0
 
 --------------------------------------------------------------------------------
 
diff --git a/dynamic-programming/src/features/knapsack/solver.js b/dynamic-programming/src/features/knapsack/solver.js
index 495b9e198445fb4ff055bf7ccd9c339b1b63a83f..02ea1b16823594b70ac85f954d2a6e5fa88dfc49 100644
--- a/dynamic-programming/src/features/knapsack/solver.js
+++ b/dynamic-programming/src/features/knapsack/solver.js
@@ -1,17 +1,44 @@
 import { Item } from './items.js';
 
+const KILOGRAM_OF_NOTHING = new Item(1, 0);
+
 class Backpointer {
-  constructor() {
-    // TODO: stub
+  constructor(item, totalValue) {
+    this.item = item;
+    this.totalValue = totalValue;
   }
 }
 
 function chooseBackpointer(items, backpointers) {
   const currentWeight = backpointers.length;
-  // TODO: stub
+  let best = new Backpointer(KILOGRAM_OF_NOTHING, backpointers[currentWeight - 1].totalValue);
+  for (const item of items) {
+    const previousWeight = currentWeight - item.weight;
+    if (previousWeight >= 0) {
+      const candidate = new Backpointer(
+        item,
+        backpointers[previousWeight].totalValue + item.value,
+      );
+      if (candidate.totalValue > best.totalValue) {
+        best = candidate;
+      }
+    }
+  }
+  return best;
 }
 
 export function chooseItems(items, weightLimit) {
   console.assert(Number.isInteger(weightLimit), `Tried to choose items with a noninteger weight limit ${weightLimit}.`);
-  return []; // TODO: stub
+  const backpointers = [new Backpointer(undefined, 0)];
+  while (backpointers.length <= weightLimit) {
+    backpointers.push(chooseBackpointer(items, backpointers));
+  }
+  const reversedPath = [];
+  for (let weight = weightLimit; weight > 0; weight -= backpointers[weight].item.weight) {
+    const item = backpointers[weight].item;
+    if (item !== KILOGRAM_OF_NOTHING) {
+      reversedPath.push(item);
+    }
+  }
+  return reversedPath.reverse();
 }