diff --git a/asymptotic-analysis.ipynb b/asymptotic-analysis.ipynb
index b745ac33c350682a29349acb480e7f3ff82b970e..d52645eeb3ba8b06fbd422133f4bb2dbca80d5d2 100644
--- a/asymptotic-analysis.ipynb
+++ b/asymptotic-analysis.ipynb
@@ -56,10 +56,9 @@
     "@dataclass\n",
     "class Student:\n",
     "    \"\"\"Represents a student with a given name and dictionary mapping course names to credits.\"\"\"\n",
-    "    # These are instance fields, not class-wide fields!\n",
+    "    # These are instance fields, not class fields!\n",
     "    name: str\n",
-    "    courses: dict[str, int] = field(default_factory=dict)\n",
-    "    # field function specifies that the default value for courses is an empty dictionary\n",
+    "    courses: dict[str, int] = field(default_factory=dict) # field function specifies that the default value for courses is an empty dictionary\n",
     "\n",
     "    def get_courses(self) -> list[str]:\n",
     "        \"\"\"Return a list of all the enrolled courses.\"\"\"\n",
@@ -85,7 +84,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# let's create a student instance for practice!"
+    "Student(\"Nicole\")"
    ]
   },
   {
@@ -103,45 +102,50 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from typing import Optional\n",
-    "\n",
-    "\n",
     "class University:\n",
-    "    \"\"\"Represents a university with a name and zero or more enrolled students.\"\"\"\n",
+    "    \"\"\"\n",
+    "    Represents one or more students enrolled in courses at a university.\n",
+    "    \"\"\"\n",
     "\n",
-    "    def __init__(self, name: str, students: Optional[list[Student]] = None) -> None:\n",
-    "        \"\"\"Initializes a new University with the given name and students (default None).\"\"\"\n",
-    "        self.name = name\n",
+    "    def __init__(self, univ_name: str, students: list[Student] | None = None) -> None:\n",
+    "        \"\"\"Takes the name of the university and optionally a list of students enrolled.\"\"\"\n",
+    "        self._name: str = univ_name\n",
     "        if students is None:\n",
-    "            self.enrolled_students = []\n",
+    "            self._students: list[Student] = []\n",
     "        else:\n",
-    "            self.enrolled_students = students\n",
-    "\n",
-    "        # self.courses: dict[str, list[Student]] = {}\n",
-    "        # for student in self.enrolled_students:\n",
-    "            # for course in student.get_courses():\n",
-    "                # if course not in self.courses:\n",
-    "                    # self.courses[course] = []\n",
-    "                # self.courses[course].append(student)\n",
-    "\n",
-    "    def students(self) -> list[Student]:\n",
-    "        \"\"\"Returns a list of all enrolled students sorted by alphabetical order.\"\"\"\n",
-    "        return sorted(self.enrolled_students, key=Student.get_name)\n",
+    "            self._students = students\n",
+    "        self._courses: dict[str, list[Student]] = {}\n",
+    "        for student in self._students:\n",
+    "            for course in student.get_courses():\n",
+    "                if course in self._courses:\n",
+    "                    self._courses[course].append(student)\n",
+    "                else:\n",
+    "                    self._courses[course] = [student]\n",
+    "\n",
+    "    def enrollments(self) -> list[Student]:\n",
+    "        \"\"\"Returns all the enrolled students sorted by their name in alphabetical order.\"\"\"\n",
+    "        sorted_students = sorted(self._students, key=lambda student:student.get_name())\n",
+    "        return sorted_students\n",
     "\n",
     "    def enroll(self, student: Student) -> None:\n",
-    "        \"\"\"Enrolls the given student in this university.\"\"\"\n",
-    "        self.enrolled_students.append(student)\n",
-    "\n",
-    "    def roster(self, course: str) -> list[Student]:\n",
-    "        \"\"\"Returns a list of all students enrolled in the given course name.\"\"\"\n",
-    "        # if course not in self.courses:\n",
-    "            # return []\n",
-    "        # return self.courses[course]\n",
-    "        students = [student for student in self.enrolled_students if course in student.get_courses()]\n",
-    "        return students\n",
-    "\n",
-    "\n",
-    "uw = University(\"Udub\", [Student(868373, \"nicole.txt\"), Student(123456, \"nicole.txt\")])\n",
+    "        \"\"\"Enrolls the student to the university.\"\"\"\n",
+    "        self._students.append(student)\n",
+    "        for course in student.get_courses():\n",
+    "            if course in self._courses:\n",
+    "                self._courses[course].append(student)\n",
+    "            else:\n",
+    "                self._courses[course] = [student]\n",
+    "\n",
+    "    def roster(self, course_name: str) -> list[Student] | None:\n",
+    "        \"\"\"\n",
+    "        Returns the list of students enrolled in the given course. If there's no students\n",
+    "        enrolled, return an empty list. If the course does not exist, return None.\n",
+    "        \"\"\"\n",
+    "        if course_name in self._courses:\n",
+    "            return self._courses[course_name]\n",
+    "        return None\n",
+    "\n",
+    "uw = University(\"Udub\", [Student(8765432, \"student.txt\"), Student(1234567, \"nicole.txt\")])\n",
     "uw.students()"
    ]
   },
@@ -154,17 +158,9 @@
     "\n",
     "The approach of precomputing and saving results in the constructor is one way to make a function more efficient than simply computing the result each time we call the function. But these aren't the only two ways to approach this problem. In fact, there's another approach that we could have taken that sits in between these two approaches. We can **cache** or **memoize** the result by computing it once and then saving the result so that we don't have to compute it again when asked for the same result in the future.\n",
     "\n",
-    "Let's start by copying and pasting the `University` class that we wrote above into the following code cell. Rename the new class to `CachedUniversity` so that we can compare them later."
+    "Let's go back to the University class to add a version of roster() that is not cached can call it `roster_slow()`."
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "78743d7f-f3bc-4844-bc19-155e37399436",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -182,7 +178,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "%time uw.roster(\"CSE163\")"
+    "%time uw.roster_slow(\"CSE163\")"
    ]
   },
   {
@@ -190,7 +186,7 @@
    "id": "80fa293b-b505-4c2b-8cd7-7a08a6f9b71a",
    "metadata": {},
    "source": [
-    "Okay, so this doesn't seem that much faster since we're only working with 2 students. There are 60,081 students enrolled across the three UW campuses. Let's try generating that many students for our courses."
+    "Okay, so this doesn't seem that much faster since we're only working with 2 students. There are 60,692 enrolled students at UW in 2023. Let's try generating that many students for our courses."
    ]
   },
   {
@@ -200,8 +196,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "names = [\"Thrisha\", \"Kevin\", \"Nicole\"]\n",
-    "students = [Student(names[i % len(names)], {\"CSE163\": 4}) for i in range(60081)]\n",
+    "names = [\"Thrisha\", \"Sheamin\", \"Nicole\"]\n",
+    "students = [Student(names[i % len(names)], {\"CSE163\": 4}) for i in range(60692)]\n",
     "students[:10]"
    ]
   },
@@ -387,7 +383,7 @@
     "\n",
     "Instead, computer scientists generally drop any constants or low-order terms from the expression *n* + 3 and just report that it \"grows like *n*.\" The notation we use is called **Big-O** notation. Instead of reporting *n* + 3 as the growth of steps, we instead report O(*n*). This means that the growth is proportional to *n* but we aren't saying the exact number of steps. We instead only report the terms that have the **biggest impact** on a function's runtime.\n",
     "\n",
-    "Let's practice this! About how many steps are run in the loop for the given method?"
+    "Poll question: count the number of steps run in the loop for the given method."
    ]
   },
   {
@@ -404,7 +400,6 @@
     "            result += j\n",
     "    return result\n",
     "\n",
-    "\n",
     "method(10)"
    ]
   },
@@ -428,7 +423,7 @@
     "\n",
     "Most operations in a set or a dictionary, such as adding values, removing values, containment check, are actually constant time or O(1) operations. No matter how much data is stored in a set or dictionary, we can always answer questions like, \"Is this value a key in this dictionary?\" in constant time.\n",
     "\n",
-    "In the [data-structures.ipynb](data-structures.ipynb) notebook, we wanted to count the number of unique words in the file, `moby-dick.txt`. We explored two different implementations that were nearly identical, except one used a `list` and the other used a `set`.\n",
+    "In the [data-structures.ipynb](data-structures.ipynb) notebook, we wanted to count the number of unique words in the file, `moby-dick.txt`. Here are two different implementations that are nearly identical, except one uses a `list` and the other uses a `set`.\n",
     "\n",
     "```py\n",
     "def count_unique_list(file_name: str) -> int:\n",