diff --git a/labs/04-priority-queues-exercises.ipynb b/labs/04-priority-queues-exercises.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..6a121d1b583cdd7373f1bdfcb46afc1f1357ded3
--- /dev/null
+++ b/labs/04-priority-queues-exercises.ipynb
@@ -0,0 +1,468 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "994d4d0c",
+   "metadata": {},
+   "source": [
+    "NAME:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "651a2e17-2fe1-41c7-be26-a7d3b40f9277",
+   "metadata": {},
+   "source": [
+    "# UE5 Fundamentals of Algorithms\n",
+    "# Lab 4: Priority queues"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "dd4f535a-2401-45c6-9d4a-ca8f146aa9c9",
+   "metadata": {},
+   "source": [
+    "---"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "615a1282-53cf-4610-bddd-2a3ba105e5bf",
+   "metadata": {},
+   "source": [
+    "<details style=\"border: 1px\">\n",
+    "<summary> How to use those notebooks</summary>\n",
+    "    \n",
+    "For each of the following questions:\n",
+    "- In the `# YOUR CODE HERE` cell, remove `raise NotImplementedError()` to write your code\n",
+    "- Write an example of use of your code or make sure the given examples and tests pass\n",
+    "- Add extra tests in the `#Tests` cell\n",
+    "    \n",
+    "</details>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b93b7332-5a40-4a85-b505-83835d68fe67",
+   "metadata": {},
+   "source": [
+    "## Exercise 1: How to use a priority queue\n",
+    "\n",
+    "Your are given a list of elements (`items`) and a matching list of priorities (`priorities`) as follows:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b91e0762-b158-44b1-850e-2987d7206b2b",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']\n",
+    "priorities = [2, 1, 3, 3, 0, 1, 1, 2]\n",
+    "\n",
+    "for item, priority in zip(items, priorities):\n",
+    "    print((item, priority))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2a348646-7633-4e79-a4ad-9597893c327d",
+   "metadata": {},
+   "source": [
+    "Use the Python [PriorityQueue](https://docs.python.org/3/library/queue.html#queue.PriorityQueue) module to store such values, and then return by _ascending_ order."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "18d02388-dca0-4493-a1c6-9d2c1314683d",
+   "metadata": {
+    "deletable": false,
+    "nbgrader": {
+     "cell_type": "code",
+     "checksum": "b4b91474b8a893af393ecf41a4177229",
+     "grade": false,
+     "grade_id": "cell-7a9ba41922a9d48b",
+     "locked": false,
+     "schema_version": 3,
+     "solution": true,
+     "task": false
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "q = PriorityQueue()\n",
+    "\n",
+    "# YOUR CODE HERE\n",
+    "raise NotImplementedError()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f44ea802-5fbf-404c-8430-0b931da0bf53",
+   "metadata": {},
+   "source": [
+    "Write a similar version that returns the `items` by _descending_ order (we assume `priorities` are always integers)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9bc92f94-97a1-46fa-813c-63fb6abc9f65",
+   "metadata": {
+    "deletable": false,
+    "nbgrader": {
+     "cell_type": "code",
+     "checksum": "4296a9b681b44c000d6aec878f352558",
+     "grade": false,
+     "grade_id": "cell-55279200308121d0",
+     "locked": false,
+     "schema_version": 3,
+     "solution": true,
+     "task": false
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "q = PriorityQueue()\n",
+    "\n",
+    "# YOUR CODE HERE\n",
+    "raise NotImplementedError()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c2ab7bd0-a237-4755-9421-a2dee82e77a4",
+   "metadata": {},
+   "source": [
+    "## Exercise 2: Implement your own priority queue\n",
+    "\n",
+    "Implement your own priority queue class and compare it to the Python module `PriorityQueue` use previously. Use the stack (or the queue) we have seen in the previous lab."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ca96c6ee-54c9-47ba-959e-60c7168256d6",
+   "metadata": {
+    "deletable": false,
+    "nbgrader": {
+     "cell_type": "code",
+     "checksum": "97ee0cb3d80986b98414224fb7f81255",
+     "grade": false,
+     "grade_id": "cell-256c1b911658a81f",
+     "locked": false,
+     "schema_version": 3,
+     "solution": true,
+     "task": false
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "class MyPriorityQueue():\n",
+    "    # YOUR CODE HERE\n",
+    "    raise NotImplementedError()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9e2f1c19-1f64-4de1-b9e8-1ed2e167cf40",
+   "metadata": {},
+   "source": [
+    "Write comparisons with the `PriorityQueue` module:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e86176bf-fb09-44a1-8732-5becb06921af",
+   "metadata": {
+    "deletable": false,
+    "nbgrader": {
+     "cell_type": "code",
+     "checksum": "a7d5a588a04dc75038101a4010a5ad1c",
+     "grade": false,
+     "grade_id": "cell-fd82f6f4e92e8816",
+     "locked": false,
+     "schema_version": 3,
+     "solution": true,
+     "task": false
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "p1 = PriorityQueue()\n",
+    "p2 = MyPriorityQueue()\n",
+    "\n",
+    "# YOUR CODE HERE\n",
+    "raise NotImplementedError()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9f0d41e9-254a-4121-a209-e751e6122e7c",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Exercise 3: Tasks scheduler\n",
+    "\n",
+    "In this exercise we want to write an algorithm that will schedule some tasks. Think of a tasks as an event with a name (eg `\"Task 1\"`, a `start` time and an `end` time). To keep it simple, we will consider times as `int`. The goal will be to write a scheduler that will decide which task to do, with an important condition: tasks cannot overlap (ie at any given moment, only 1 task can be picked).  You'll start by defining a `Task` class and then the `TaskScheduler` class that manages the list of tasks you picked and that do not overlap.\n",
+    "\n",
+    "Start by writing the `Task` class and in particular include a way to compare tasks by overriding the [comparison operator](https://docs.python.org/3/library/operator.html):\n",
+    "\n",
+    "```python\n",
+    "    def __lt__(self, other):\n",
+    "        return self.start < other.start\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "44aacc95-2aa6-485b-8759-ac292244f290",
+   "metadata": {
+    "deletable": false,
+    "nbgrader": {
+     "cell_type": "code",
+     "checksum": "31d7c78de176602c4028bc2873ba2eaa",
+     "grade": false,
+     "grade_id": "cell-8eeb5fa664a2abf2",
+     "locked": false,
+     "schema_version": 3,
+     "solution": true,
+     "task": false
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "class Task:\n",
+    "    # YOUR CODE HERE\n",
+    "    raise NotImplementedError()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6430a61e-879d-4c2d-bc92-a6f288786242",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "task1 = Task(\"Task 1\", 1, 4)\n",
+    "task2 = Task(\"Task 2\", 2, 5)\n",
+    "assert task1 < task2"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7563c27b-c412-4899-acb3-57c10f2abb8d",
+   "metadata": {},
+   "source": [
+    "Now write the `TaskScheduler` class. You can use the `PriorityQueue` or your own implementation of a priority queue. Tip: write 3 methods:\n",
+    "\n",
+    "1. add a task\n",
+    "2. check if overlapping\n",
+    "3. return the non-overlapping tasks"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6e9d6120-ffb9-4a2f-9fb6-e3c0761ecf75",
+   "metadata": {
+    "deletable": false,
+    "nbgrader": {
+     "cell_type": "code",
+     "checksum": "eb1328388c170d781a8878d366569388",
+     "grade": false,
+     "grade_id": "cell-e47f1725f66b5081",
+     "locked": false,
+     "schema_version": 3,
+     "solution": true,
+     "task": false
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "from queue import PriorityQueue\n",
+    "\n",
+    "class TaskScheduler:\n",
+    "    # YOUR CODE HERE\n",
+    "    raise NotImplementedError()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "558776cd-9f8f-404e-bc7f-803c4fbbae55",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "# Usage example\n",
+    "s = TaskScheduler()\n",
+    "\n",
+    "t1 = Task(\"Task 1\", 1, 4)\n",
+    "t2 = Task(\"Task 2\", 2, 5)  # overlap\n",
+    "t3 = Task(\"Task 3\", 5, 6)\n",
+    "\n",
+    "s.put(t1)\n",
+    "s.put(t2)  # cannot add (overlap)\n",
+    "s.put(t3)\n",
+    "\n",
+    "for task in s.get():\n",
+    "    print(f\"{task.name}: [{task.start}, {task.end}]\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "14c5196d-d45e-4314-9678-60548fe2f811",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Tests\n",
+    "s = TaskScheduler()\n",
+    "assert s.get() == []"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2911284c-7c5b-4c65-a426-dac028d6bd4f",
+   "metadata": {},
+   "source": [
+    "## Exercise 4: Merge sorted lists (using heaps)\n",
+    "\n",
+    "The `heapq` [(doc)](https://docs.python.org/3/library/heapq.html) module provides an efficient implementation of priority queue using the heap sorting algorithm, that use can use instead of the `PriorityQueue` class. Here is an example on how to use the heap. The operations complexity are are:\n",
+    "- `heapify` $O(n)$ as it find the smallest value (only) to return next\n",
+    "- `heappop` $O(log(n))$ as it finds again the smallest value (only) to return next, but in an efficient way"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "da1fbbad-7bb6-4f89-a446-bdb308a545ac",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import heapq\n",
+    "nums = [10, 20, 5, 7, 9, 15, 3]\n",
+    "heapq.heapify(nums)\n",
+    "sorted_list = [heapq.heappop(nums) for _ in range(len(nums))]\n",
+    "sorted_list"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "327aa702-5699-42ba-96e1-b75079d31050",
+   "metadata": {},
+   "source": [
+    "Write a function `merge_sorted_lists(lists)` that takes a list of sorted lists, and uses `heapq` to merge them into a single sorted list as a result.\n",
+    "\n",
+    "1. Use `heapq` to store the first element of each list.\n",
+    "2. Store each element in the heap as a tuple `(value, list_index, element_index)`:\n",
+    "   - `value` is the element value,\n",
+    "   - `list_index` is the index of the list it comes from,\n",
+    "   - `element_index` is the position of the element in its original list.\n",
+    "3. At each step, extract the smallest element from the heap and add it to the result list.\n",
+    "4. Insert the next element from the same list into the heap until all lists are empty."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4b20f11a-47b8-419a-b94a-902943048c76",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "import heapq\n",
+    "\n",
+    "def merge(lists):\n",
+    "    min_heap = []\n",
+    "    merged_list = []\n",
+    "\n",
+    "    for i, lst in enumerate(lists): # each first element in heap\n",
+    "        if lst:\n",
+    "            heapq.heappush(min_heap, (lst[0], i, 0))  # (value, list_index, element_index)\n",
+    "\n",
+    "    while min_heap:\n",
+    "        val, list_index, element_index = heapq.heappop(min_heap)\n",
+    "        merged_list.append(val)\n",
+    "\n",
+    "        if element_index + 1 < len(lists[list_index]):\n",
+    "            next_val = lists[list_index][element_index + 1]\n",
+    "            heapq.heappush(min_heap, (next_val, list_index, element_index + 1))\n",
+    "\n",
+    "    return merged_list"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1b7e1364-1335-4daf-9100-ef933f97b86d",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "# Usage example\n",
+    "lists = [\n",
+    "    [1, 4, 5],\n",
+    "    [1, 3, 4],\n",
+    "    [2, 6]\n",
+    "]\n",
+    "\n",
+    "merge(lists) # [1, 1, 2, 3, 4, 4, 5, 6]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "44600161-32a0-4b84-b152-65a15ba103ae",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "# Tests\n",
+    "assert merge([[]]) == []\n",
+    "assert merge([[1, 2, 3]]) == [1, 2, 3]"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.9"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}