I typically write a raytracer while attempting to stick to a small set of "best-practices" for the language I'm trying to learn. I also use this exercise from time-to-time to test new language features (for example C++20 ranges/coroutines recently).

I also recommend this same exercise to others through the book "The Ray Tracer Challenge" [1] by Jamis Buck, because it describes an entire implementation of a raytracer in pseudocode. Beginners mostly just need to plug-in their language of choice & still end up with a satisfying visual result. Experienced programmers can extend it by adding the ability to load non-trivial .obj models, which will necessarily motivate adding concurrency, bounding volumes, & other general performance improvements.

I'm planning on tackling it again in Elixir soon, which should be fun & interesting.

EDIT: Almost forgot to mention the free bonus chapters [2] that get you started on bounding volumes (AABB), soft area lights, and texture mapping.

[1] https://pragprog.com/titles/jbtracer/the-ray-tracer-challeng...

[2] http://www.raytracerchallenge.com/#bonus

I'm currently going through this book and it's amazing. Any tips on where to go once I finish it?

Once you finish the main book, there's a few free bonus chapters provided [1] that help you implement soft area lights, bounding boxes (AABB in this case), and texture mapping. Beyond that, you might be interested in the "One Weekend" series [2], which is all C++ but is mostly general enough for other languages. There's also PBRT [3] which is an open source textbook/documentation for the pbrt-v3 renderer [4], but this is beyond the scope of "small exercise" territory. Good luck!

[1] http://www.raytracerchallenge.com/#bonus

[2] https://raytracing.github.io/

[3] https://pbr-book.org/3ed-2018/contents

[4] https://github.com/mmp/pbrt-v3