diff --git a/2021/README.md b/2021/README.md
index f92b694..36ecdbb 100644
--- a/2021/README.md
+++ b/2021/README.md
@@ -2,33 +2,33 @@
 
 ## Progress
 
-| Day | Part 1 | Part 2 | Language |
-| :-: | :----: | :----: | :------- |
-| 01  |   ✓    |   ✓    | [Ruby]   |
-| 02  |   ✓    |   ✓    | [Ada]    |
-| 03  |   ✓    |   ✓    | [Perl]   |
-| 04  |   ✓    |   ✓    | [Go]     |
-| 05  |   ✓    |   ✓    | [Nim]    |
-| 06  |   ✓    |   ✓    | [Inko]   |
-| 07  |   ✓    |   ✓    | [Swift]  |
-| 08  |   ✓    |   ✓    | [C#]     |
-| 09  |   ✓    |   ✓    | [C]      |
-| 10  |   ✓    |   ✓    | [OCaml]  |
-| 11  |        |        |          |
-| 12  |        |        |          |
-| 13  |        |        |          |
-| 14  |        |        |          |
-| 15  |        |        |          |
-| 16  |        |        |          |
-| 17  |        |        |          |
-| 18  |        |        |          |
-| 19  |        |        |          |
-| 20  |        |        |          |
-| 21  |        |        |          |
-| 22  |        |        |          |
-| 23  |        |        |          |
-| 24  |        |        |          |
-| 25  |        |        |          |
+| Day | Part 1 | Part 2 | Language  |
+| :-: | :----: | :----: | :-------- |
+| 01  |   ✓    |   ✓    | [Ruby]    |
+| 02  |   ✓    |   ✓    | [Ada]     |
+| 03  |   ✓    |   ✓    | [Perl]    |
+| 04  |   ✓    |   ✓    | [Go]      |
+| 05  |   ✓    |   ✓    | [Nim]     |
+| 06  |   ✓    |   ✓    | [Inko]    |
+| 07  |   ✓    |   ✓    | [Swift]   |
+| 08  |   ✓    |   ✓    | [C#]      |
+| 09  |   ✓    |   ✓    | [C]       |
+| 10  |   ✓    |   ✓    | [OCaml]   |
+| 11  |   ✓    |   ✓    | [Crystal] |
+| 12  |        |        |           |
+| 13  |        |        |           |
+| 14  |        |        |           |
+| 15  |        |        |           |
+| 16  |        |        |           |
+| 17  |        |        |           |
+| 18  |        |        |           |
+| 19  |        |        |           |
+| 20  |        |        |           |
+| 21  |        |        |           |
+| 22  |        |        |           |
+| 23  |        |        |           |
+| 24  |        |        |           |
+| 25  |        |        |           |
 
 ## Additional Challenges
 
@@ -46,3 +46,4 @@
 [c#]: https://docs.microsoft.com/en-us/dotnet/csharp/
 [c]: http://www.open-std.org/jtc1/sc22/wg14
 [ocaml]: https://ocaml.org
+[crystal]: https://crystal-lang.org
diff --git a/2021/day-11/Justfile b/2021/day-11/Justfile
new file mode 100644
index 0000000..1b34677
--- /dev/null
+++ b/2021/day-11/Justfile
@@ -0,0 +1,6 @@
+@part PART INPUT_FILE="inputs/puzzle.txt":
+    crystal build --error-trace part_{{PART}}.cr
+    ./part_{{PART}} {{INPUT_FILE}}
+
+clean:
+    rm -f part_one part_two
diff --git a/2021/day-11/common.cr b/2021/day-11/common.cr
new file mode 100644
index 0000000..4a4b8b7
--- /dev/null
+++ b/2021/day-11/common.cr
@@ -0,0 +1,70 @@
+alias OctopusMap = Array(Array(Int32))
+alias FlashMap = Array(Array(Bool))
+
+SURROUNDING = [-1, 0, 1]
+  .repeated_permutations(2)
+  .reject { |v| v == [0, 0] }
+  .map { |v| Tuple(Int32, Int32).from(v) }
+  .to_set
+
+def parse_file(path : String) : OctopusMap
+  File.read_lines(path, chomp: true).map &.chars.map(&.to_i)
+end
+
+def print_map(octopi : OctopusMap)
+  puts "-" * octopi.first.size
+  octopi.each do |row|
+    puts row
+  end
+  puts "-" * octopi.first.size
+end
+
+def flash(x : Int32, y : Int32, octopi : OctopusMap, flashes : FlashMap) : Int32
+  flashes[y][x] = true
+  octopi[y][x] = 0
+
+  return 1 + mark_surrounding(x, y, octopi, flashes) do |nx, ny|
+    next 0 if flashes[ny][nx]
+    next flash(nx, ny, octopi, flashes) if octopi[ny][nx] > 9
+    0
+  end
+end
+
+def mark_surrounding(x : Int32, y : Int32, octopi : OctopusMap, flashes : FlashMap) : Int32
+  valid_surrounding = SURROUNDING.reject do |(dx, dy)|
+    val = false
+    val ||= dx == -1 if x == 0
+    val ||= dx == 1 if x == octopi.first.size - 1
+    val ||= dy == -1 if y == 0
+    val ||= dy == 1 if y == octopi.size - 1
+    val
+  end
+
+  return valid_surrounding.sum do |(dx, dy)|
+    octopi[y + dy][x + dx] += 1 unless flashes[y + dy][x + dx]
+    yield x + dx, y + dy
+  end
+end
+
+def generation(octopi : OctopusMap, flashes : FlashMap) : Int32
+  gen_flash_count = 0
+
+  octopi.each_with_index do |row, y|
+    row.each_with_index do |_, x|
+      next if flashes[y][x]
+      octopi[y][x] += 1
+      gen_flash_count += flash(x, y, octopi, flashes) if octopi[y][x] > 9
+    end
+  end
+
+  gen_flash_count
+end
+
+def generations(steps : Int32, octopi : OctopusMap, flashes : FlashMap, flash_count : Int32) : Int32
+  return flash_count if steps == 0
+
+  flash_count += generation(octopi, flashes)
+
+  flashes = octopi.map &.map { false }
+  return generations(steps - 1, octopi, flashes, flash_count)
+end
diff --git a/2021/day-11/inputs/puzzle.txt b/2021/day-11/inputs/puzzle.txt
new file mode 100644
index 0000000..1a0fab9
--- /dev/null
+++ b/2021/day-11/inputs/puzzle.txt
@@ -0,0 +1,10 @@
+4134384626
+7114585257
+1582536488
+4865715538
+5733423513
+8532144181
+1288614583
+2248711141
+6415871681
+7881531438
diff --git a/2021/day-11/inputs/sample.txt b/2021/day-11/inputs/sample.txt
new file mode 100644
index 0000000..03743f6
--- /dev/null
+++ b/2021/day-11/inputs/sample.txt
@@ -0,0 +1,10 @@
+5483143223
+2745854711
+5264556173
+6141336146
+6357385478
+4167524645
+2176841721
+6882881134
+4846848554
+5283751526
diff --git a/2021/day-11/part_one.cr b/2021/day-11/part_one.cr
new file mode 100644
index 0000000..dd7a8c1
--- /dev/null
+++ b/2021/day-11/part_one.cr
@@ -0,0 +1,10 @@
+require "./common"
+
+def main(path)
+  octopi = parse_file(path)
+  flashes = octopi.map &.map { false }
+
+  puts generations(100, octopi, flashes, 0)
+end
+
+main(ARGV.first)
diff --git a/2021/day-11/part_two.cr b/2021/day-11/part_two.cr
new file mode 100644
index 0000000..b443e04
--- /dev/null
+++ b/2021/day-11/part_two.cr
@@ -0,0 +1,18 @@
+require "./common"
+
+def main(path)
+  octopi = parse_file(path)
+
+  req_flashes = octopi.size * octopi.first.size
+
+  (1..).each do |generation|
+    flashes = octopi.map &.map { false }
+    gen_flashes = generation(octopi, flashes)
+    if gen_flashes == req_flashes
+      puts generation
+      break
+    end
+  end
+end
+
+main(ARGV.first)
diff --git a/README.md b/README.md
index e188ad2..38b14bc 100644
--- a/README.md
+++ b/README.md
@@ -8,4 +8,4 @@
 -   [2018](2018/README.md) (0% completed)
 -   [2019](2019/README.md) (0% completed)
 -   [2020](2020/README.md) (16% completed)
--   [2021](2021/README.md) (40% completed)
+-   [2021](2021/README.md) (44% completed)