diff --git a/graph-search/notes.md b/graph-search/notes.md
index 03f649113f94bfbd9659c4915b6a2a6fb908dd92..76ec7a4496bfa8c40eed7fbff6eaa7f9fa5b13c9 100644
--- a/graph-search/notes.md
+++ b/graph-search/notes.md
@@ -103,31 +103,54 @@ How many moves does it take to reverse the string `123`?
 
 ## Best-First Search
 
-*   Worklist: …
-*   Priority: …
+*   Worklist: Priority Queue
+*   Priority: Distance traveled from the source plus the estimated distance to the destination
 *   Guaranteed to find a path if one exists
 *   Not guaranteed to find a good path (but often does anyway)
 *   Can have good best- and average-case time efficiency
 
-      Worklist       Backpointers
-    ------------    --------------
-    (⊥, a, 0)
+         Worklist          Backpointers
+    ------------------    --------------
+    (⊥, a, 0) → 2 ✓       a → (⊥, a, 0)
+    (a, c, 7) → 9 ✓       c → (a, c, 7)
+    (c, e, 12) → 13 ✓     b → (c, b, 8)
+    (c, b, 8) → 9 ✓       e → (c, e, 12)
+    (b, e, 17) → 18       d → (e, d, 15)
+    (b, a, 12) → 14 ✓
+    (e, d, 15) → 15 ✓
+    (e, f, 14) → 16
 
     Reversed Path
     ----
-    …
+    d ← e ← c ← a
 
 ## A*
 
-*   Worklist: …
-*   Priority: …
+*   Worklist: Priority Queue
+*   Priority: Distance traveled from the source plus the estimated distance to the destination (estimated by an *admissible* heuristic)
 *   Guaranteed to find a path with least weight
 *   Can have good best- and average-case time efficiency
 
 ## Recursive Depth-First Search (DFS)
 
-*   Worklist: …
+*   Worklist: Program Stack
 
              Activation Frames            Backpointers      Returned
     -----------------------------------   ------------ ------------------
-    edge = (⊥, a), incidence = …
+    edge = (⊥, a), incidence = (a, c)     a → (⊥, a)   [a, c, b, e, f, d]
+    edge = (a, c), incidence = (c, b)     c → (a, c)   [c, b, e, f, d]
+    edge = (c, b), incidence = (b, e)     b → (c, b)   [b, e, f, d]
+    edge = (b, a)                                      ⊥
+    edge = (b, e), incidence = (e, f)     e → (b, e)   [e, f, d]
+    edge = (e, f), incidence = (f, d)     f → (e, f)   [f, d]
+    edge = (f, d)                         d → (f, d)   [d]
+
+             Activation Frames               Visited        Returned
+    -----------------------------------   ------------ ------------------
+    source = a, incidence = (a, c)        a            [a, c, b, e, f, d]
+    source = c, incidence = (c, b)        c            [c, b, e, f, d]
+    source = b, incidence = (b, e)        b            [b, e, f, d]
+    source = a                                         ⊥
+    source = e, incidence = (e, f)        e            [e, f, d]
+    source = f, incidence = (f, d)        f            [f, d]
+    source = d                            d            [d]
diff --git a/graph-search/src/features/search/search.js b/graph-search/src/features/search/search.js
index 86a91cc8b90ef22a0df1eb259c5e2dc70643abb1..d53d46109f289424e735663245bb5d5216339200 100644
--- a/graph-search/src/features/search/search.js
+++ b/graph-search/src/features/search/search.js
@@ -92,13 +92,62 @@ export function dijkstras(graph, source, destination) {
 }
 
 function heuristic(vertex, destination) {
-  return 0; // TODO: stub
+  if ('acf'.includes(vertex)) {
+    return 2;
+  }
+  if ('be'.includes(vertex)) {
+    return 1;
+  }
+  return 0;
 }
 
 export function bestFirst(graph, source, destination) {
-  return undefined; // TODO: stub
+  const backpointers = new Map();
+  const worklist = new PriorityQueue();
+  worklist.insert(
+    new Edge(undefined, source, 0),
+    0 + heuristic(source, destination),
+  );
+  while (worklist.size > 0) {
+    const workitem = worklist.remove();
+    if (backpointers.has(workitem.to) &&
+      backpointers.get(workitem.to).distance <= workitem.distance) {
+      continue;
+    }
+    backpointers.set(workitem.to, workitem);
+    if (workitem.to === destination) {
+      const reversedPath = [];
+      for (let current = destination;
+        current !== undefined;
+        current = backpointers.get(current).from) {
+        reversedPath.push(current);
+      }
+      return reversedPath.reverse();
+    }
+    for (const incidence of graph.getIncidences(workitem.to)) {
+      worklist.insert(
+        new Edge(workitem.to, incidence.destination, workitem.distance + incidence.weight),
+        workitem.distance + incidence.weight +
+          heuristic(incidence.destination, destination),
+      );
+    }
+  }
+  return undefined;
 }
 
-export function recursiveDFS(graph, source, destination) {
-  return undefined; // TODO: stub
+export function recursiveDFS(graph, source, destination, visited = new Set()) {
+  if (visited.has(source)) {
+    return undefined;
+  }
+  visited.add(source);
+  if (source === destination) {
+    return [source];
+  }
+  for (const incidence of [...graph.getIncidences(source)].reverse()) {
+    const suffix = recursiveDFS(graph, incidence.destination, destination, visited);
+    if (suffix !== undefined) {
+      return [source, ...suffix];
+    }
+  }
+  return undefined;
 }
diff --git a/graph-search/src/features/search/solution.js b/graph-search/src/features/search/solution.js
index a64ab4729a95691560c0aae8d3e2fe73cbf91fb3..94ab48c52e1bb295c71130825092d5b6c81942c1 100644
--- a/graph-search/src/features/search/solution.js
+++ b/graph-search/src/features/search/solution.js
@@ -29,7 +29,7 @@ function formatSolution(solution) {
 }
 
 export function Solution(props) {
-  const search = dijkstras;
+  const search = recursiveDFS;
   const firstSolution = search(firstExample, 'a', 'd');
   const secondSolution = search(secondExample, '123', '321');
   return (