Skip to Content

Advent of Code 2025 - Day 1, in Kotlin - Secret Entrance

Kotlin solutions to parts 1 and 2 of Advent of Code 2025, Day 1: 'Secret Entrance'

Posted on

It’s December 1st and that means we’re back for another year of Advent of Code! This year there will only be 12 days and no global leaderboard. Personally, I’m glad for both of these changes. While 25 puzzles is loads of fun, it’s a massive toll on Eric Wastl’s personal time and I’m happy for anything we can get. If 12 is what we get, that’s great. As for the global leaderboard, AI can solve most of these puzzles quasi-instantly, so there’s no real point in having one.

I’m going to try and solve the puzzle each day using Kotlin and blogging about the solutions. Even with only 12 puzzles, doing this is like having a second job, and I will spend as much time as my work and personal time commitments allow.

If you’d rather just view the code, my GitHub Repository is here.

Before We Start: A Note on AI

I will be doing these puzzles without using AI. I code with IntelliJ and do not have any AI assistants (such as JetBrains Junie) or plugins enabled. I also commit to not asking non-integrated AIs (ChatGPT, Claude, etc.) for help.

Occasionally, I’ll get help from somebody on the Kotlin Language Slack or on Reddit and in those cases I will call out that I got help and from where. This commitment extends all the way through my effort from solving the puzzles, to optimizing my solution, to writing these blog posts.

If you like using these tools, please don’t take this as any kind of judgement on your efforts. You code how you code, and that’s great. I use Advent of Code as a learning experience and AI gets in the way of that, for me.

Puzzle Input

Our first task is to parse our input into something more usable. For this we’ll write a parseInput function to turn our List<String> into a List<Int> where each Int represents a rotation instruction. Negative values mean a leftward rotation, positive values mean a rightward rotation.


class Day01(input: List<String>) {

    private val rotations: List<Int> = parseInput(input)

    private fun parseInput(input: List<String>): List<Int> =
    input.map {
        val amount = it.drop(1).toInt()
        when (it[0]) {
            'L' -> -amount
            else -> amount
        }
    }
}

⭐ Day 1, Part 1

The puzzle text can be found here.

For part 1, we will mimic the turn of the dial by using a runningFold, which is a normal left-to-right fold where all of the intermediate values are kept. The runningFold is seeded with a start value representing the dial setting at the start of the puzzle (which is 50, not 0 as I had misread initially).

Each step of the fold takes the rotation, either a positive or negative number, and adds it to the previous value of the dial, performs a mod(100) on the result, to normalize the resulting dial position between 0 and 99, and returns it.

At the end of the runningFold, we will have a List<Int> where each value represents the dial setting after each rotation instruction. We count the number of values where the dial stopped at 0 for our answer.

// In Day01

fun solvePart1(): Int =
    rotations.runningFold(50) { dial, rotation ->
        (dial + rotation).mod(100)
    }.count { it == 0 }

Star earned! Onward!

⭐ Day 1, Part 2

The puzzle text can be found here.

Part 2 is a bit more complex because we have to count the number of times the dial passes through or lands on zero. This took me longer than I care to admit to figure out, but we got there in the end.

Since we will need to track the position of the dial and the number of zeros we’ve seen, our fold will pass along a Pair<Int, Int>, where the first value is the dial setting and the second value is the number of zeros we’ve seen so far. We don’t need the intermediate values in this case, so we can use a fold instead of a runningFold. Note that the fold is seeded with a Pair(50, 0) (meaning: dial position 50, we have seen no zeros so far). Also note that we destructure the Pair into dial and zeros to make our code a bit cleaner.

For each rotation instruction, we need to calculate the numberOfZeroCrossings, which depends on the direction of the rotation. For a rightward rotation (rotation is positive), we can add the dial to the rotation and divide by 100.

For leftward rotations (rotation is negative), the answer is a bit more complicated. When the dial is on zero, we need to divide the absolute value of the rotation value by 100. Picture the dial on 0 and a rotation of -250. Intuitively, we know that we’ll pass back through zero 2 times because 250 / 100 is 2 (when rounding to an Int). All other cases require us to take the size of the dial into account by subtracting it from the absolute value of the rotation and adding the size of the dial (100). As with a rightward rotation, we need to divide by 100.

// In Day01

fun solvePart2(): Int =
    rotations.fold(Pair(50, 0)) { (dial, zeros), rotation ->
        val numberOfZeroCrossings: Int = if (rotation > 0) {
            (dial + rotation) / 100
        } else when (dial) {
            0 -> -rotation / 100
            else -> (-rotation - dial + 100) / 100
        }

        Pair(
            (dial + rotation).mod(100),
            zeros + numberOfZeroCrossings
        )
    }.second

At the end of the rotation instruction we bundle our current dial position (see part 1!) and the new count for the number of zeros we’ve seen into a new Pair. Taking the second value from the end result Pair gives us our answer.

We could have written solvePart2() as a single expression by moving the numberOfZeroCrossings calculation inline, but I think this is easier to read.

Star earned! See you tomorrow!

Further Reading

  1. Index of All Solutions - All posts and solutions for 2025, in Kotlin.
  2. My Github repo - Solutions and tests for each day.
  3. Solution - Full code for day 1
  4. Advent of Code - Come join in and do these challenges yourself!