diff --git a/2015/input/2015_9.txt b/2015/input/2015_9.txt
new file mode 100644
index 0000000..a56f5e2
--- /dev/null
+++ b/2015/input/2015_9.txt
@@ -0,0 +1,28 @@
+AlphaCentauri to Snowdin = 66
+AlphaCentauri to Tambi = 28
+AlphaCentauri to Faerun = 60
+AlphaCentauri to Norrath = 34
+AlphaCentauri to Straylight = 34
+AlphaCentauri to Tristram = 3
+AlphaCentauri to Arbre = 108
+Snowdin to Tambi = 22
+Snowdin to Faerun = 12
+Snowdin to Norrath = 91
+Snowdin to Straylight = 121
+Snowdin to Tristram = 111
+Snowdin to Arbre = 71
+Tambi to Faerun = 39
+Tambi to Norrath = 113
+Tambi to Straylight = 130
+Tambi to Tristram = 35
+Tambi to Arbre = 40
+Faerun to Norrath = 63
+Faerun to Straylight = 21
+Faerun to Tristram = 57
+Faerun to Arbre = 83
+Norrath to Straylight = 9
+Norrath to Tristram = 50
+Norrath to Arbre = 60
+Straylight to Tristram = 27
+Straylight to Arbre = 81
+Tristram to Arbre = 90
diff --git a/2015/lib/2015/9.ex b/2015/lib/2015/9.ex
new file mode 100644
index 0000000..5274b09
--- /dev/null
+++ b/2015/lib/2015/9.ex
@@ -0,0 +1,72 @@
+import AOC
+
+aoc 2015, 9 do
+  def parse_line(line) do
+    [from, _, to, _, distance] = String.split(line, " ")
+    {from, to, String.to_integer(distance)}
+  end
+
+  def p1 do
+    graph =
+      input_stream()
+      |> Stream.map(&parse_line/1)
+      |> Enum.reduce(Graph.new(type: :undirected), fn {v1, v2, weight}, graph ->
+        Graph.add_edge(graph, v1, v2, weight: weight)
+      end)
+
+    for start <- Graph.vertices(graph), reduce: :infinity do
+      shortest_so_far -> find_shortest_path_to_all_vertices(graph, start) |> min(shortest_so_far)
+    end
+  end
+
+  def find_shortest_path_to_all_vertices(graph, start, distance \\ 0)
+
+  def find_shortest_path_to_all_vertices(%{vertices: v}, _, distance) when map_size(v) == 1,
+    do: distance
+
+  def find_shortest_path_to_all_vertices(graph, start, distance) do
+    for neighbor <- Graph.neighbors(graph, start), reduce: :infinity do
+      shortest_path_distance ->
+        %{weight: distance_to_neighbor} = Graph.edge(graph, start, neighbor)
+
+        find_shortest_path_to_all_vertices(
+          Graph.delete_vertex(graph, start),
+          neighbor,
+          distance_to_neighbor + distance
+        )
+        |> min(shortest_path_distance)
+    end
+  end
+
+  def p2 do
+    graph =
+      input_stream()
+      |> Stream.map(&parse_line/1)
+      |> Enum.reduce(Graph.new(type: :undirected), fn {v1, v2, weight}, graph ->
+        Graph.add_edge(graph, v1, v2, weight: weight)
+      end)
+
+    for start <- Graph.vertices(graph), reduce: 0 do
+      longest_so_far -> find_longest_path_to_all_vertices(graph, start) |> max(longest_so_far)
+    end
+  end
+
+  def find_longest_path_to_all_vertices(graph, start, distance \\ 0)
+
+  def find_longest_path_to_all_vertices(%{vertices: v}, _, distance) when map_size(v) == 1,
+    do: distance
+
+  def find_longest_path_to_all_vertices(graph, start, distance) do
+    for neighbor <- Graph.neighbors(graph, start), reduce: 0 do
+      longest_path_distance ->
+        %{weight: distance_to_neighbor} = Graph.edge(graph, start, neighbor)
+
+        find_longest_path_to_all_vertices(
+          Graph.delete_vertex(graph, start),
+          neighbor,
+          distance_to_neighbor + distance
+        )
+        |> max(longest_path_distance)
+    end
+  end
+end
diff --git a/2015/lib/advent_of_code2015.ex b/2015/lib/advent_of_code2015.ex
deleted file mode 100644
index 645ed7b..0000000
--- a/2015/lib/advent_of_code2015.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-defmodule AdventOfCode2015 do
-  @moduledoc """
-  Documentation for `AdventOfCode2015`.
-  """
-
-  @doc """
-  Hello world.
-
-  ## Examples
-
-      iex> AdventOfCode2015.hello()
-      :world
-
-  """
-  def hello do
-    :world
-  end
-end
diff --git a/2015/mix.exs b/2015/mix.exs
index 02415aa..853ea3f 100644
--- a/2015/mix.exs
+++ b/2015/mix.exs
@@ -23,6 +23,7 @@ defmodule AdventOfCode2015.MixProject do
     [
       {:advent_of_code_utils, "~> 1.0"},
       {:exla, "~> 0.1.0-dev", github: "elixir-nx/nx", sparse: "exla"},
+      {:libgraph, "~> 0.13.3"},
       {:nimble_parsec, "~> 1.0"},
       {:nx, "~> 0.1.0-dev", github: "elixir-nx/nx", sparse: "nx", override: true}
     ]
diff --git a/2015/mix.lock b/2015/mix.lock
index 767627d..236fcce 100644
--- a/2015/mix.lock
+++ b/2015/mix.lock
@@ -2,6 +2,7 @@
   "advent_of_code_utils": {:hex, :advent_of_code_utils, "1.0.0", "e9ce8be22988e095991168e9d2dde802afc213aaae64491e9fde84dd877db41c", [:mix], [], "hexpm", "1fa5217ccb95a38ce2312dd7ea6057b0d0002a3eba7490feab704df4b8a94038"},
   "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
   "exla": {:git, "https://github.com/elixir-nx/nx.git", "e23a678bf0ebcbbafe03f1b5ed78623f052ad486", [sparse: "exla"]},
+  "libgraph": {:hex, :libgraph, "0.13.3", "20732b7bafb933dcf7351c479e03076ebd14a85fd3202c67a1c197f4f7c2466b", [:mix], [], "hexpm", "78f2576eef615440b46f10060b1de1c86640441422832052686df53dc3c148c6"},
   "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
   "nx": {:git, "https://github.com/elixir-nx/nx.git", "e23a678bf0ebcbbafe03f1b5ed78623f052ad486", [sparse: "nx"]},
   "xla": {:hex, :xla, "0.2.0", "689887888afb22587168d461f0e9ff83d7b06040273ea7082dbf9ff7eca33dcc", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "a2e7b81413db49a159eabfb12dbd784a7c04b5c68c7b4057238d5ec9b110f2ec"},
diff --git a/2015/test/advent_of_code2015_test.exs b/2015/test/advent_of_code2015_test.exs
deleted file mode 100644
index 5c9ee79..0000000
--- a/2015/test/advent_of_code2015_test.exs
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule AdventOfCode2015Test do
-  use ExUnit.Case
-  doctest AdventOfCode2015
-
-  test "greets the world" do
-    assert AdventOfCode2015.hello() == :world
-  end
-end