diff --git a/graph-search/notes.md b/graph-search/notes.md
index 9414791dbc7f7216fa30b949078c65d9f35c49a9..7d29ac8eb026d5cb292f8a5b6e91bd39fdbc77c4 100644
--- a/graph-search/notes.md
+++ b/graph-search/notes.md
@@ -61,51 +61,74 @@ In the weighted directed graph on the whiteboard, what is the shortest path from
 
 ## Breadth-First Search (BFS)
 
-*   Worklist: …
+*   Worklist: Queue
 *   Guaranteed to find a path with the fewest edges
 
     Worklist    Backpointers
     --------    ------------
-    (⊥, a)
+    (⊥, a) ✓    a → (⊥, a)
+    (a, c) ✓    c → (a, c)
+    (c, e) ✓    e → (c, e)
+    (c, b) ✓    b → (c, b)
+    (e, d) ✓    d → (e, d)
+    (e, f)
+    (b, e)
+    (b, a)
 
     Reversed Path
     ----
-    …
+    d ← e ← c ← a
 
 ## Dijkstra's Algorithm
 
-*   Worklist: …
-*   Priority: …
+*   Worklist: Priority Queue
+*   Priority: Distance travelled from the source
 *   Guaranteed to find a path with least weight
 
       Worklist       Backpointers
     ------------    --------------
-    (⊥, a, 0)
+    (⊥, a, 0) ✓     a → (⊥, a, 0)
+    (a, c, 7) ✓     c → (a, c, 7)
+    (c, e, 12) ✓    b → (c, b, 8)
+    (c, b, 8) ✓     e → (c, e, 12)
+    (b, e, 17)      f → (e, f, 14)
+    (b, a, 12) ✓    d → (f, d, 14)
+    (e, d, 15)
+    (e, f, 14) ✓
+    (f, c, 22)
+    (f, d, 14) ✓
 
     Reversed Path
     ----
-    …
+    d ← f ← e ← c ← a
 
 ## Best-First Search
 
-*   Worklist: …
-*   Priority: …
+*   Worklist: Priority Queue
+*   Priority: Distance travelled from the source plus estimated distance still to travel
 *   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, a, 12) → 14 ✓  d → (e, d, 15)
+    (b, e, 17) → 18
+    (e, d, 15) → 15 ✓
+    (e, f, 14) → 16
 
     Reversed Path
     ----
-    …
+    d ← e ← c ← a
 
 ## A*
 
-*   Worklist: …
-*   Priority: …
+*   Worklist: Priority Queue
+*   Priority: Distance travelled from the source plus estimated distance still to travel (estimates computed with an *admissible* heuristic)
 *   Guaranteed to find a path with least weight
 *   Can have good best- and average-case time efficiency
 
diff --git a/graph-search/src/features/search/search.js b/graph-search/src/features/search/search.js
index a30fcf5c7b79551f86fdc33cd23f47619a6daeb8..b60729c296d395a7f362a9f73639831b5f335a26 100644
--- a/graph-search/src/features/search/search.js
+++ b/graph-search/src/features/search/search.js
@@ -37,19 +37,98 @@ export function dfs(graph, source, destination) {
 }
 
 export function bfs(graph, source, destination) {
-  return undefined; // TODO: stub
+  const backpointers = new Map();
+  const worklist = new Queue();
+  worklist.insert(new Edge(undefined, source));
+  while (worklist.size > 0) {
+    const workitem = worklist.remove();
+    if (backpointers.has(workitem.to)) {
+      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));
+    }
+  }
+  return undefined;
 }
 
 export function dijkstras(graph, source, destination) {
-  return undefined; // TODO: stub
+  const backpointers = new Map();
+  const worklist = new PriorityQueue();
+  worklist.insert(new Edge(undefined, source, 0), 0);
+  while (worklist.size > 0) {
+    const workitem = worklist.remove();
+    if (backpointers.has(workitem.to)) {
+      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,
+      );
+    }
+  }
+  return undefined;
 }
 
 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), 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) {
diff --git a/graph-search/src/features/search/solution.js b/graph-search/src/features/search/solution.js
index 69644f05b7179674513f148f766ecd8ae3c0b252..da0ee237318884f3c7338c8127591d408bf84d54 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 = dfs;
+  const search = bestFirst;
   const firstSolution = search(firstExample, 'a', 'd');
   const secondSolution = search(secondExample, '123', '321');
   return (