diff --git a/previous-exams/2021-midterm-solution.md b/previous-exams/2021-midterm-solution.md
new file mode 100644
index 0000000000000000000000000000000000000000..0caef211231ccfa526dfb6dae45b0a89c4c136e7
--- /dev/null
+++ b/previous-exams/2021-midterm-solution.md
@@ -0,0 +1,457 @@
+# 2021 Midterm, Solutions
+
+## Exercise 1
+
+```scala
+trait Tree[T]:
+    def height: Int = this match
+        case EmptyTree(_) => 0
+        case Node(left, _, right, _) => Math.max(left.height, right.height) + 1
+    
+    def isBalanced: Boolean = this match
+        case EmptyTree(_) => true
+        case Node(left, _, right, _) => left.isBalanced && right.isBalanced && Math.abs(left.height - right.height) <= 1
+
+case class EmptyTree[T](leq: (T, T) => Boolean) extends Tree[T]
+case class Node[T](left: Tree[T], elem: T, right: Tree[T], leq: (T, T) => Boolean)
+extends Tree[T]
+```
+
+<details>
+
+<summary>Tests</summary>
+
+```scala
+def intLeq(a: Int, b: Int) = a <= b
+
+val exTree1 = Node(
+    Node(
+        Node(EmptyTree(intLeq), 1, EmptyTree(intLeq), intLeq),
+        2,
+        Node(EmptyTree(intLeq), 3, EmptyTree(intLeq), intLeq),
+        intLeq
+    ),
+    5,
+    Node(
+        Node(EmptyTree(intLeq), 7, EmptyTree(intLeq), intLeq),
+        9,
+        Node(
+            Node(EmptyTree(intLeq), 10, EmptyTree(intLeq), intLeq),
+            15,
+            Node(EmptyTree(intLeq), 25, EmptyTree(intLeq), intLeq),
+            intLeq
+        ),
+        intLeq
+    ),
+    intLeq
+)
+// exTree1: Node[Int] = Node(
+//   Node(
+//     Node(
+//       EmptyTree(repl.MdocSession$App$$Lambda$26768/0x0000000802a74000@96de2cb),
+//       1,
+//       EmptyTree(repl.MdocSession$App$$Lambda$26769/0x0000000802a745d0@7b8c0297),
+//       repl.MdocSession$App$$Lambda$26770/0x0000000802a74ba0@b9d9006
+//     ),
+//     2,
+//     Node(
+//       EmptyTree(repl.MdocSession$App$$Lambda$26771/0x0000000802a754c0@76af1792),
+//       3,
+//       EmptyTree(repl.MdocSession$App$$Lambda$26772/0x0000000802a68000@10b2d836),
+//       repl.MdocSession$App$$Lambda$26773/0x0000000802a685d0@bb1f80d
+//     ),
+//     repl.MdocSession$App$$Lambda$26774/0x0000000802a68ba0@6c4b54dd
+//   ),
+//   5,
+//   Node(
+//     Node(
+//       EmptyTree(repl.MdocSession$App$$Lambda$26775/0x0000000802a69170@6256cd41),
+//       7,
+//       EmptyTree(repl.MdocSession$App$$Lambda$26776/0x0000000802a69740@64a3da55),
+//       repl.MdocSession$App$$Lambda$26777/0x0000000802a4c000@60d6275c
+//     ),
+//     9,
+//     Node(
+//       Node(
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26778/0x0000000802a4c5d0@560d6a27
+//         ),
+//         10,
+//         EmptyTree(repl.MdocSession$App$$Lambda$26779/0x0000000802a4cba0@2e58962),
+//         repl.MdocSession$App$$Lambda$26780/0x0000000802a4d170@786e8a19
+//       ),
+//       15,
+//       Node(
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26781/0x0000000802a4d740@29d3be8f
+//         ),
+//         25,
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26782/0x00000008023d8000@1432a5c0
+//         ),
+//         repl.MdocSession$App$$Lambda$26783/0x00000008023d85d0@3ab290bd
+//       ),
+//       repl.MdocSession$App$$Lambda$26784/0x00000008023d8ba0@3600ebb0
+//     ),
+// ...
+
+val exTree2 = Node(
+    Node(
+        Node(
+            Node(EmptyTree(intLeq), 25, EmptyTree(intLeq), intLeq),
+            15,
+            Node(EmptyTree(intLeq), 10, EmptyTree(intLeq), intLeq),
+            intLeq
+        ),
+        9,
+        Node(EmptyTree(intLeq), 7, EmptyTree(intLeq), intLeq),
+        intLeq
+    ),
+    5,
+    Node(
+        Node(EmptyTree(intLeq), 3, EmptyTree(intLeq), intLeq),
+        2,
+        Node(EmptyTree(intLeq), 1, EmptyTree(intLeq), intLeq),
+        intLeq
+    ),
+    intLeq
+)
+// exTree2: Node[Int] = Node(
+//   Node(
+//     Node(
+//       Node(
+//         EmptyTree(repl.MdocSession$App$$Lambda$26787/0x0000000802af2000@38eb51e),
+//         25,
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26788/0x0000000802af25d0@2d17faaa
+//         ),
+//         repl.MdocSession$App$$Lambda$26789/0x0000000802af2ba0@1cfbd9d3
+//       ),
+//       15,
+//       Node(
+//         EmptyTree(repl.MdocSession$App$$Lambda$26790/0x0000000802af3170@613fca1),
+//         10,
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26791/0x0000000802af3740@70439f38
+//         ),
+//         repl.MdocSession$App$$Lambda$26792/0x0000000802a4e000@6293f930
+//       ),
+//       repl.MdocSession$App$$Lambda$26793/0x0000000802a4e5d0@206481f0
+//     ),
+//     9,
+//     Node(
+//       EmptyTree(repl.MdocSession$App$$Lambda$26794/0x00000008023da000@390b97a9),
+//       7,
+//       EmptyTree(repl.MdocSession$App$$Lambda$26795/0x00000008023da5d0@55dd75ef),
+//       repl.MdocSession$App$$Lambda$26796/0x0000000802af1000@287a5c1d
+//     ),
+//     repl.MdocSession$App$$Lambda$26797/0x0000000802af15d0@2c9523e3
+//   ),
+//   5,
+//   Node(
+//     Node(
+//       EmptyTree(repl.MdocSession$App$$Lambda$26798/0x0000000802b50000@49464f07),
+//       3,
+//       EmptyTree(repl.MdocSession$App$$Lambda$26799/0x0000000802b505d0@778a39cd),
+//       repl.MdocSession$App$$Lambda$26800/0x0000000802b50ba0@d6a0995
+//     ),
+//     2,
+//     Node(
+//       EmptyTree(repl.MdocSession$App$$Lambda$26801/0x0000000802b51170@865a9e0),
+//       1,
+//       EmptyTree(repl.MdocSession$App$$Lambda$26802/0x0000000802b51740@7320e19c),
+//       repl.MdocSession$App$$Lambda$26803/0x0000000802b51d10@55c2fa64
+//     ),
+//     repl.MdocSession$App$$Lambda$26804/0x0000000802b522e0@83967e8
+// ...
+
+val tree3 = Node(
+    Node(
+        Node(
+            Node(EmptyTree(intLeq), 25, EmptyTree(intLeq), intLeq),
+            15,
+            Node(EmptyTree(intLeq), 10, EmptyTree(intLeq), intLeq),
+            intLeq
+        ),
+        9,
+        Node(EmptyTree(intLeq), 7, EmptyTree(intLeq), intLeq),
+        intLeq
+    ),
+    5,
+    EmptyTree(intLeq),
+    intLeq
+)
+// tree3: Node[Int] = Node(
+//   Node(
+//     Node(
+//       Node(
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26806/0x0000000802b52e80@2d36e5a6
+//         ),
+//         25,
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26807/0x0000000802b53450@596ecd9d
+//         ),
+//         repl.MdocSession$App$$Lambda$26808/0x0000000802b53a20@1bbd0d0e
+//       ),
+//       15,
+//       Node(
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26809/0x0000000802b53ff0@58783fb6
+//         ),
+//         10,
+//         EmptyTree(
+//           repl.MdocSession$App$$Lambda$26810/0x0000000802b545c0@42f9faca
+//         ),
+//         repl.MdocSession$App$$Lambda$26811/0x0000000802b54b90@6dd4c52d
+//       ),
+//       repl.MdocSession$App$$Lambda$26812/0x0000000802b55160@1e7270e4
+//     ),
+//     9,
+//     Node(
+//       EmptyTree(repl.MdocSession$App$$Lambda$26813/0x0000000802b55730@76e6913),
+//       7,
+//       EmptyTree(repl.MdocSession$App$$Lambda$26814/0x0000000802b55d00@a4cec9e),
+//       repl.MdocSession$App$$Lambda$26815/0x0000000802b562d0@41fabd57
+//     ),
+//     repl.MdocSession$App$$Lambda$26816/0x0000000802b568a0@4c0210dc
+//   ),
+//   5,
+//   EmptyTree(repl.MdocSession$App$$Lambda$26817/0x0000000802b56e70@3850cebe),
+//   repl.MdocSession$App$$Lambda$26818/0x0000000802b57440@5515fd36
+// )
+
+assert(exTree1.height == 4)
+assert(exTree2.height == 4)
+assert(EmptyTree(intLeq).height == 0)
+assert(exTree1.isBalanced)
+assert(exTree2.isBalanced)
+assert(!tree3.isBalanced)
+```
+
+</details>
+
+## Exercise 2
+
+```scala
+enum Expr:
+  case Var(name: String)
+  case Op(name: String, args: List[Expr])
+
+import Expr._
+
+final case class UnknownVarException(name: String) extends Exception
+final case class UnknownOpException(name: String) extends Exception
+```
+
+There was multiple correct solutions. Here two possible solutions:
+
+```scala
+def eval1(e: Expr, context: Map[Var, Int]): Int = e match 
+    case Var(name) => context.get(Var(name)) match
+        case Some(n) => n
+        case _ => throw UnknownVarException(name)
+    case Op(name, args) =>
+        val nargs = args.map(eval1(_, context))
+        name match
+            case "*" => nargs.foldLeft(1)(_ * _)
+            case "+" => nargs.foldLeft(0)(_ + _)
+            case _ => throw UnknownOpException(name)
+
+def eval2(e: Expr, context: Map[Var, Int]): Int = e match 
+    case sym: Var if context.contains(sym) => context(sym)
+    case sym: Var => throw UnknownVarException(sym.name)
+    case Op("*", args) => args.foldLeft(1)(_ * eval2(_, context))
+    case Op("+", args) => args.foldLeft(0)(_ + eval2(_, context))
+    case Op(name, _) => throw UnknownOpException(name)
+```
+
+<details>
+
+<summary>Tests</summary>
+
+```scala
+for eval <- Seq(eval1, eval2) do
+    assert(eval(Op("+", List()), Map()) == 0)
+    assert(eval(Op("+", List(Var("x"))), Map(Var("x") -> 2)) == 2)
+    assert(eval(Op("+", List(Var("x"), Var("y"))), Map(Var("x") -> 2, Var("y") -> 3)) == 5)
+    assert(eval(Op("*", List()), Map()) == 1)
+    assert(eval(Op("*", List(Var("x"))), Map(Var("x") -> 2)) == 2)
+    assert(eval(Op("*", List(Var("x"), Var("y"))), Map(Var("x") -> 2, Var("y") -> 3)) == 6)
+    assert(eval(Op("*", List(Op("+", List(Var("x"), Var("x"))), Var("y"))), Map(Var("x") -> 2, Var("y") -> 3)) == 12)
+```
+
+</details>
+
+## Exercise 3
+
+### Exercise 3.1
+
+- Type parameter `A` in `map`
+- `def map[B >: A](f: B => C): Transform[B, C]`
+
+### Exercise 3.2
+
+```
+Is the following subtype assertion true?                yes    no
+
+List[Triangle]            <:  List[AnyRef]              [X]    [ ]
+List[Shape]               <:  List[AnyVal]              [ ]    [X]
+List[String]              <:  List[Shape]               [ ]    [X]
+Transform[Point, Circle]  <:  Transform[Point, Any]     [X]    [ ]
+Transform[Point, Shape]   <:  Transform[Shape, Point]   [ ]    [X]
+Transform[Shape, Point]   <:  Transform[Point, Shape]   [X]    [ ]
+```
+
+## Exercise 4
+
+Axioms:
+
+```
+(1) Nil.reverse === Nil
+
+(2) (x::xs).reverse === xs.reverse ++ (x::Nil)
+
+(3) (xs++ys)++zs === xs++(ys++zs)
+
+(4) Nil map f === Nil
+
+(5) (x::xs) map f === f(x) :: (xs map f)
+
+(6) Nil ++ ys === ys
+
+(7) xs ++ Nil === xs
+
+(8) x::xs ++ ys === x::(xs ++ ys)
+
+(9) (l1 ++ l2).map(f) === l1.map(f) ++ l2.map(f)
+```
+
+### Exercise 4.1
+
+
+```
+We must prove:
+
+(10) (l1 ++ l2).reverse === l2.reverse ++ l1.reverse
+
+
+By induction on l1:
+
+- l1 is Nil:
+
+        (Nil ++ l2).reverse ===
+    (6) l2.reverse
+
+        l2.reverse ++ Nil.reverse ===
+    (1) l2.reverse ++ Nil ===
+    (7) l2.reverse
+
+- l1 is x::xs
+
+    IH: (xs ++ l2).reverse === l2.reverse ++ xs.reverse
+
+         (x::xs ++ l2).reverse ===
+    (8)  (x::(xs ++ l2)).reverse ===
+    (2)  (xs ++ l2).reverse ++ (x::Nil) ===
+    (IH) l2.reverse ++ xs.reverse ++ x::Nil ===
+    (3)  l2.reverse ++ (xs.reverse ++ x::Nil) ===
+    (2)  l2.reverse ++ (x::xs).reverse ===
+         l2.reverse ++ l1.reverse
+```
+
+### Exercise 4.2
+
+```
+We must prove:
+
+(l1 ++ l2).reverse.map(f) === l2.reverse.map(f) ++ l1.reverse.map(f)
+
+
+Direct proof using axiom 10 proved in 4.1:
+
+     (l1 ++ l2).reverse.map(f) =
+(10) (l2.reverse ++ l1.reverse).map(f) =
+(9)  l2.reverse.map(f) ++ l1.reverse.map(f)
+
+
+Or without using axiom 10:
+
+By induction on l1:
+
+- l1 is Nil:
+
+        ((l1 ++ l2).reverse).map(f) ===
+    (6) l2.reverse.map.(f)
+
+        l2.reverse.map(f) ++ l1.reverse.map(f) ===
+    (1) l2.reverse.map(f) ++ Nil.map(f) ===
+    (4) l2.reverse.map(f) ++ Nil ===
+    (7) l2.reverse.map(f)
+
+
+- l1 is x::xs
+
+    IH: ((xs ++ l2).reverse).map(f) === l2.reverse.map(f) ++ xs.reverse.map(f)
+
+         ((x::xs) ++ l2).reverse.map(f) ===
+    (8)  (x::(xs ++ l2)).reverse.map(f) ===
+    (2)  ((xs ++ l2).reverse ++ (x::Nil)).map(f) ===
+    (9)  ((xs ++ l2).reverse.map(f)) ++ ((x::Nil).map(f)) ===
+    (IH) l2.reverse.map(f) ++ xs.reverse.map(f) ++ (x::Nil).map(f) ===
+    (3)  l2.reverse.map(f) ++ (xs.reverse.map(f) ++ (x::Nil).map(f)) ===
+    (9)  l2.reverse.map(f) ++ (xs.reverse ++ x::Nil).map(f)) ===
+    (2)  l2.reverse.map(f) ++ ((x::xs).reverse.map(f)) ===
+```
+
+## Exercise 5
+
+### Exercise 5.1
+
+There was multiple correct solutions:
+
+```scala
+def takeWhileStrictlyIncreasing1(list: List[Int]): List[Int] = list match
+    case Nil => Nil
+    case x :: Nil => list
+    case x1 :: x2 :: xs => if x1 < x2 then x1 :: takeWhileStrictlyIncreasing1(x2 :: xs) else List(x1)
+
+def takeWhileStrictlyIncreasing2(list: List[Int]): List[Int] = {
+    def fun(ls: List[Int], last: Int): List[Int] = ls match
+        case x :: xs if x > last => x :: fun(xs, x)
+        case _ => Nil
+
+    list match
+        case x :: xs => x :: fun(xs, x)
+        case Nil => Nil
+}
+
+def takeWhileStrictlyIncreasing3(list: List[Int]): List[Int] = {
+    def fun(ls: List[Int], last: Option[Int]): List[Int] = (ls, last) match
+        case (x :: xs, Some(last)) if x > last => x :: fun(xs, Some(x))
+        case (x :: xs, None) => x :: fun(xs, Some(x))
+        case _ => Nil
+
+    fun(list, None)
+}
+```
+
+
+**Comments**
+- It was possible to write a correct implementation using foldLeft. Note that foldLeft traverses the whole list and cannot "early return" like a recursive call. This possible solution was not very clean as it required a flag or a condition to check that foldLeft should do nothing once it passed the first decreasing step. Avoid using foldLeft when you do not need to traverse the whole list every time.
+- When we do not ask for a tail recursive implementation, the solution can be more readable without an accumulator. You should favor a simple and readable version.
+- The solution `takeWhileStrictlyIncreasing3` uses `Option` and is elaborate. We include this solution for a good example of using `Option` but `takeWhileStrictlyIncreasing2` is simpler and more readable.
+
+### Exercise 5.2
+
+```scala
+def increasingSequences(list: List[Int]): List[List[Int]] = list match
+    case Nil => Nil
+    case _ =>
+        val prefix = takeWhileStrictlyIncreasing(list)
+        prefix :: increasingSequences(list.drop(prefix.length))
+```
+
+**Comments**
+- It is important to store `prefix` to avoid calling twice `takeWhileStrictlyIncreasing`.
+- In the empty list case, some students made the confusion of returning a list containing the empty list. In this case, we have: `List[List[Int]]() == Nil != List(Nil) == List(List[Int]())`.
\ No newline at end of file