diff --git a/Labs/Lab5/dfs-class/README.md b/Labs/Lab5/dfs-class/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..35b27469feaf64ccd1a6c92162b99111ccd946bc
--- /dev/null
+++ b/Labs/Lab5/dfs-class/README.md
@@ -0,0 +1,157 @@
+# Inheritance and DFS
+
+## Inheritance
+
+Paste and run the following code in a new python notebook called debug.ipynb in your p3 folder:
+
+```python
+class Parent:
+    def twice(self):
+        self.message()
+        self.message()
+        
+    def message(self):
+        print("parent says hi")
+        
+class Child:
+    def message(self):
+        print("child says hi")
+        
+c = Child()
+```
+
+Modify `Child` so that it inherits from `Parent`.
+
+What do you think will be printed if you call `c.twice()`?  Discuss
+with your group, then run it to find out.
+
+When `self.some_method(...)` is called, and there are multiple methods
+named `some_method` in your program, the type of `self` (the original object that is calling the method from the class) is what
+matters for determining which one runs.  It doesn't matter where the
+`self.some_method(...)` is (could be any method).
+
+## GraphSearcher
+
+Copy and paste the following starter code (which you'll build on in the project):
+
+```python
+class GraphSearcher:
+    def __init__(self):
+        self.visited = set()
+        self.order = []
+
+    def visit_and_get_children(self, node):
+        """ Record the node value in self.order, and return its children
+        param: node
+        return: children of the given node
+        """
+        raise Exception("must be overridden in sub classes -- don't change me here!")
+
+    def dfs_search(self, node):
+        # 1. clear out visited set and order list
+        # 2. start recursive search by calling dfs_visit
+
+    def dfs_visit(self, node):
+        # 1. if this node has already been visited, just `return` (no value necessary)
+        # 2. mark node as visited by adding it to the set
+        # 3. call self.visit_and_get_children(node) to get the children
+        # 4. in a loop, call dfs_visit on each of the children
+```
+
+The graphs we search on come in many shapes and formats
+(e.g. matrices, files or web), but it would be nice if we could use
+the same depth-first search (DFS) code when we want to search
+different kinds of graphs.  Therefore, we would like to implement a
+base class `GraphSearcher` and implement the DFS algorithm in it.
+
+For our purposes, we aren't using DFS to find a specific path.  We
+just want to see what nodes are reachable from a given starting
+`node`, so these methods don't need to return any value.  Your job is
+to replace the comments in `dfs_search` and `dfs_visit` with code
+(some comments may require a couple lines of code).
+
+The `dfs_visit` method will call `visit_and_get_children` to record
+the node value and determine the children of a given node. Subclasses
+of `GraphSearcher` can override `visit_and_get_children` to lookup the
+children of a node in different kinds of graphs (e.g. matrices, files
+or web).
+
+
+Try your code:
+
+```python
+g = GraphSearcher()
+g.dfs_search("A")
+```
+
+You should get an exception.  The purpose of `GraphSearcher` is not to
+directly create objects, it is to let other clases inherit
+`dfs_search` (we'll do the inheritance soon).
+
+## Matrix Format
+
+Paste and run the following:
+
+```python
+import pandas as pd
+
+df = pd.DataFrame([
+    [0,1,0,1],
+    [0,0,1,0],
+    [0,0,0,1],
+    [0,0,1,0],
+], index=["A", "B", "C", "D"], columns=["A", "B", "C", "D"])
+df
+```
+
+A grid of ones and zeros like this is a common way to represent
+directed graphs.  A `1` in the "C" column of the "B" row means that
+there is an edge from node B to node C.
+
+Try drawing a directed graph on a piece of paper based on the above
+grid.
+
+`df.loc["????"]` looks up a row in a DataFrame.  Use it to lookup the
+children of node B.
+
+Complete the following to print all the children of "B" (should only be "C"):
+
+```python
+for node, has_edge in df.loc["B"].items():
+    if ????:
+        print(????)
+```
+
+Let's create a class that inherits from `GraphSearcher` and works with
+graphs represented as matrices:
+
+```python
+class MatrixSearcher(????):
+    def __init__(self, df):
+        super().????() # call constructor method of parent class
+        self.df = df
+
+    def visit_and_get_children(self, node):
+        # TODO: Record the node value in self.order
+        children = []
+        # TODO: use `self.df` to determine what children the node has and append them
+        return children
+```
+
+Complete the `????` and `TODO` parts.  Test it, checking what nodes
+are reachable from each starting point:
+
+```python
+m = MatrixSearcher(df)
+m.dfs_search(????)
+m.order
+```
+
+From "A", for example, `m.order` should be `['A','B', 'C', 'D']`.  Look
+back at the picture you drew of the graph and make sure you're getting
+what you expect when starting from other nodes.
+
+## scrape.py
+
+If you've been doing this work in a notebook, you should now move your
+code to a new module called `scrape.py` in your `p3` directory.
diff --git a/Labs/Lab5/dfs-vs-bfs/1.png b/Labs/Lab5/dfs-vs-bfs/1.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f79e63acf89a3ae0a3ed4afc8d10485aeaffcf5
Binary files /dev/null and b/Labs/Lab5/dfs-vs-bfs/1.png differ
diff --git a/Labs/Lab5/dfs-vs-bfs/2.png b/Labs/Lab5/dfs-vs-bfs/2.png
new file mode 100644
index 0000000000000000000000000000000000000000..872e2d34696bf058238b35b688fa5dd73c3f0a62
Binary files /dev/null and b/Labs/Lab5/dfs-vs-bfs/2.png differ
diff --git a/Labs/Lab5/dfs-vs-bfs/3.png b/Labs/Lab5/dfs-vs-bfs/3.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d6adce9b308f4a8366c439a7678963dead0462f
Binary files /dev/null and b/Labs/Lab5/dfs-vs-bfs/3.png differ
diff --git a/Labs/Lab5/dfs-vs-bfs/4.png b/Labs/Lab5/dfs-vs-bfs/4.png
new file mode 100644
index 0000000000000000000000000000000000000000..ccf2b07d6d7804cbd47fcb3a236c0597ce26c595
Binary files /dev/null and b/Labs/Lab5/dfs-vs-bfs/4.png differ
diff --git a/Labs/Lab5/dfs-vs-bfs/README.md b/Labs/Lab5/dfs-vs-bfs/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a470749c0eca2a5862cf3c8100dd53744b0f655
--- /dev/null
+++ b/Labs/Lab5/dfs-vs-bfs/README.md
@@ -0,0 +1,231 @@
+# DFS vs. BFS
+
+In this lab, you'll get practice with depth-first search and
+breadth-first search with some interactive exercises.
+
+Start a new notebook on your virtual machine, then paste+run this code
+in a cell (you don't need to read it):
+
+```python
+from IPython.display import display, HTML
+from graphviz import Digraph
+
+class test_graph:
+    def __init__(self):
+        self.nodes = {}
+        self.traverse_order = None # in what order were nodes checked?
+        self.next_guess = 0
+        self.colors = {}
+
+    def node(self, name):
+        name = str(name).upper()
+        self.nodes[name] = Node(self, name)
+
+    def edge(self, src, dst):
+        src, dst = str(src).upper(), str(dst).upper()
+        for name in [src, dst]:
+            if not name in self.nodes:
+                self.node(name)
+        self.nodes[src].children.append(self.nodes[dst])
+
+    def _repr_svg_(self):
+        g = Digraph(engine='neato')
+        for n in self.nodes:
+            g.node(n, fillcolor=self.colors.get(n, "white"), style="filled")
+            children = self.nodes[n].children
+            for i, child in enumerate(children):
+                g.edge(n, child.name, penwidth=str(len(children) - i), len="1.5")
+        return g._repr_image_svg_xml()
+
+    def dfs(self, src, dst):
+        src, dst = str(src).upper(), str(dst).upper()
+        self.traverse_order = []
+        self.next_guess = 0
+        self.colors = {}
+        self.visited = set()
+        self.path = self.nodes[src].dfs(dst)
+        display(HTML("now call .visit(???) to identify the first node explored"))
+        display(self)
+
+    def bfs(self, src, dst):
+        src, dst = str(src).upper(), str(dst).upper()
+        self.traverse_order = []
+        self.next_guess = 0
+        self.colors = {}
+        self.path = self.nodes[src].bfs(dst)
+        display(HTML("now call .visit(???) to identify the first node explored"))
+        display(self)
+    
+    def visit(self, name):
+        name = str(name).upper()
+        if self.traverse_order == None:
+            print("please call dfs or bfs first")
+        if self.next_guess == len(self.traverse_order):
+            print("no more nodes to explore")
+            return
+        self.colors = {}
+        for n in self.traverse_order[:self.next_guess]:
+            self.colors[n] = "yellow"
+        if name == self.traverse_order[self.next_guess]:
+            display(HTML("Correct..."))
+            self.colors[name] = "yellow"
+            self.next_guess += 1
+        else:
+            display(HTML("<b>Oops!</b> Please guess again."))
+            self.colors[name] = "red"
+        display(self)
+        if self.next_guess == len(self.traverse_order):
+            if self.path == None:
+                display(HTML("You're done, there is no path!"))
+            else:
+                seq = input("What path was found? [enter nodes, comma separated]: ")
+                seq = tuple(map(str.strip, seq.upper().split(",")))
+                if seq == tuple(map(str.upper, self.path)):
+                    print("Awesome!!!")
+                else:
+                    print("actually, expected was: ", ",".join(self.path))
+
+    
+class Node:
+    def __init__(self, graph, name):
+        self.graph = graph
+        self.name = name
+        self.children = []
+
+    def __repr__(self):
+        return "node %s" % self.name
+
+    def dfs(self, dst):
+        if self.name in self.graph.visited:
+            return None
+        
+        self.graph.traverse_order.append(self.name)
+        
+        self.graph.visited.add(self.name)
+
+        if self.name == dst:
+            return (self.name, )
+        for child in self.children:
+            childpath = child.dfs(dst)
+            if childpath:
+                return (self.name, ) + childpath
+        return None
+
+    def backtrace(self):
+        nodes = []
+        node = self
+        while node != None:
+            nodes.append(node.name)
+            node = node.back
+        return tuple(reversed(nodes))
+
+    def bfs(self, dst):
+        added = set()
+        todo = [self]
+        self.back = None
+        added.add(self.name)
+
+        while len(todo) > 0:
+            curr = todo.pop(0)
+            self.graph.traverse_order.append(curr.name)
+
+            if curr.name == dst:
+                return curr.backtrace()
+            else:
+                for child in curr.children:
+                    if not child.name in added:
+                        todo.append(child)
+                        child.back = curr
+                        added.add(child.name)
+
+        return None
+```
+
+## Problem 1 [4-node, DFS]
+
+Paste the following to a cell:
+
+```python
+g = test_graph()
+g.edge(1, 2)
+g.edge(4, 3)
+g.edge(1, 3)
+g.edge(2, 4)
+g
+```
+
+It should look something like this:
+
+<img src="1.png" width=300>
+
+Node 1 has two children: nodes 2 and 3.  The thicker line to node 2
+indicates node 2 is in the `children` list before node 3.
+
+Let's do a DFS from node 1 to 3.  Paste the following:
+
+```python
+g.dfs(1, 3)
+```
+
+You should see something like this:
+
+<img src="2.png" width=500>
+
+Try calling the visit function with
+
+```python
+g.visit(1)
+```
+
+The visited node should look like this:
+
+<img src="3.png" width=300>
+
+Keep making `g.visit(????)` calls until you complete the depth first search.
+
+Once the target node is reach, you'll be prompted to enter the path
+from source to destination.  Do so and type enter to check your
+answer:
+
+<img src="4.png" width=600>
+
+## Problem 2 [4-node, BFS]
+
+Paste+run the following(same graph structure as last time, but you'll
+visit the nodes in a different order by doing a BFS):
+
+```python
+g = test_graph()
+g.edge(1, 2)
+g.edge(4, 3)
+g.edge(1, 3)
+g.edge(2, 4)
+g.bfs(1, 3)
+```
+
+## Problem 3 [7-node, DFS+BFS]
+
+Paste+run the following:
+
+```python
+g = test_graph()
+for i in range(5):
+    g.edge(i, i+1)
+    g.edge(i, 6)
+    g.edge(6, i)
+g.dfs(0, 4)
+```
+
+Then change `dfs` to `bfs` and try again.
+
+## Problem 4 [6-node, BFS]
+
+```python
+g = test_graph()
+for i in range(0, 4, 2):
+    g.edge(i, i+2)
+    g.edge(i+1, i+3)
+    g.edge(i, i+1)
+g.edge(4, 5)
+g.bfs(2, 1)
+```
diff --git a/Labs/Lab5/lab5.md b/Labs/Lab5/lab5.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd8b4a115df9cad2bb3fb0947940daa7c4ffd2de
--- /dev/null
+++ b/Labs/Lab5/lab5.md
@@ -0,0 +1,5 @@
+# Lab 5: Graph Search
+
+1. Practice [graph search order](./dfs-vs-bfs)
+
+2. Start the [module](./dfs-class) you'll be building for P3
\ No newline at end of file