Spoiler alert

This is a full solution for Advent of Code 2025, day 1 puzzles with explanations.

If you want to try to solve it yourself first, head over to https://adventofcode.com/2025/day/1 and give it a go and then come back and compare notes.

Weehee, we’re on the way with another year of lovely Advent of Code puzzles. This year, the Elves have learned project management so there shouldn’t be any issues… oh wait, something’s wrong again.

The password to enter North Pole is in a safe and we need to open the safe.

The safe has a round dial with numbers from 0 to 99 and we’re provided a list of instructions that look like this:

L17
L20
R6
L1
R12

where the first character is either L or R, denoting direction (left or right) and is followed by a positive integer that tells us how far we rotate.

Read input

I have a custom read_input function that I’ve written years ago to reduce the cognitive load of figuring out how to read the data so I can focus on problem solving. Here’s its signature:

def read_input[T](
    day: int, map_fn: Callable[[str], T] = str, example: bool = False
) -> List[T]:

It’s given a day which tells it which input file to read, a map_fn which is a function that takes in a string and returns whatever and a boolean example which decides if we’re reading in example data or real data.

map_fn is applied to each line in the input. Here, since our data is in format of XY where X is a single character L or R and Y is a positive integer, I started with the following map function:

def mapper(line: str) -> Tuple[str, int]:
    """Input lines are in format of
    XY where 
    - X is direction of either 'R' or 'L' and 
    - Y is a positive integer
 
    Returns a tuple of (direction, amount)
    """
    direction = line[0]
    amount = int(line[1:])
 
    return direction, amount

It takes the first character as direction and converts the rest into an integer for amount and returns them as a tuple.

In a real-life situation, it would be good to have some error handling here in case of invalid inputs but since we’re dealing with a puzzle environment, we can trust that Eric has provided us with valid inputs only.

Part 1

For the first part, the password is

The actual password is the number of times the dial is left pointing at 0 after any rotation in the sequence.

def part_1() -> int:
    """
    Calculate how many times the dial stops at 0
    at the end of an instruction.
    """
    rotations = read_input(1, mapper)
    current = 50
    hits_at_zero = 0
 
    for direction, amount in rotations:
        if direction == 'L':
            current -= amount
        elif direction == 'R':
            current += amount
 
        # Keep values within bounds of 0—99
        current %= 100
        
        if current == 0:
            hits_at_zero += 1
 
    return hits_at_zero

We read the rotation instructions, start the dial at 50 as instructed and keep track of the times the dial hits zero.

Then, for each instruction we rotate it left or right and using modulo (%) operation, we keep the value always between 0 and 99. If the new value at the end of a rotation is 0, we count it for our password!

That’s the first ⭐️ of the year in the bag.

Part 2

For the second part, it’s not enough to calculate when it stops at 0 but also any time it hits 0 during a rotation. So if we’d go from 5 to 95, we go across 0 and must count it.

I started with a really simple solution just to get a result and then refactored it to require less computation.

def part_2() -> int:
    """
    Calculate how many times the dial hits 0
    during an instruction.
    """
    rotations = read_input(1, mapper, example=False)
    current = 50
 
    hits_at_zero = 0
    for direction, amount in rotations:
        if direction == 'L':
            multiplier = -1
        elif direction == 'R':
            multiplier = 1
 
        for i in range(amount):
            current += multiplier
            current %= 100
            
            if current == 0:
                hits_at_zero += 1
 
    return hits_at_zero

Here, I ran every step of every rotation and checked if we had hit 0 or not. I wanted to share this solution because while it’s not really one that I wanted to finish the day with (despite getting me a correct result), it was the first and simplest solution that came to my mind.

There are many ways to solve puzzles and depending on your skills in programming and mathematics, you may end up with different solutions. That’s perfectly fine. You gotta start where you are and especially in these early day puzzles this kind of solution runs just fine — later on in the month your computer will probably not finish all the calculations with such brute force solution though so keep that in mind.

Once I was confident I got a correct result, I wanted to refactor that extra for loop away.

def part_2() -> int:
    """
    Calculate how many times the dial hits 0
    during an instruction.
    """
    rotations = read_input(1, mapper)
    current = 50
 
    hits_at_zero = 0
    for direction, amount in rotations:
        prev = current
 
        full_rotations, leftover = divmod(amount, 100)
        hits_at_zero += full_rotations
 
        if direction == 'L':
            current -= leftover
        elif direction == 'R':
            current += leftover
 
        # We've hit 0:
        #  If we go over 100
        if current > 100:
            hits_at_zero += 1
        #  If we go below 0 (and didn't start at 0)
        elif current < 0 and prev != 0:
            hits_at_zero += 1
        #  If we land on exactly zero
        elif current % 100 == 0:
            hits_at_zero += 1
        
        current %= 100
 
    return hits_at_zero

Here, I start by calculating how many times the rotation crosses 0 regardless of the starting point. If we rotate 417 steps, we know we cross 0 at least 4 times because we do 400+ rotations. After that, the leftover is a rotation that has less than 100 steps. We can get both values with Python’s builtin divmod function.

Then, we calculate any hits with leftovers:

  • if those leftover rotations take us over 100, we know we crossed 0.
  • if we didn’t start at zero and ended up in the negative, we know we crossed 0
  • and finally, if we land exactly on zero, we count that too

Then at the end of the loop, we normalise the new position with modulo (%) again to make sure we stay within the 0-99 range.

And that, my friend is another ⭐️ in the bag with the first day successfully solved and we’re 1/12 of the way to decorating North Pole and saving Christmas.

asserts for refactoring protection

Once I get a correct result that is confirmed by the website, I add an assert next to my results:

if __name__ == '__main__':
    part_1_result = part_1()
    print(f'Part 1: {part_1_result}')
    assert part_1_result == 1043, 'Incorrect answer for part 1'
 
    part_2_result = part_2()
    print(f'Part 2: {part_2_result}')
    assert part_2_result == 5963, 'Incorrect answer for part 2'

If you try to run these with your own input, they will likely both fail because everyone gets a custom input. I use this so that when I eventually start to refactor and clean up the code, I can always run it and make sure it’s still producing the same result.

First day successful!

That brings us to the end of day 1, welcome along the journey!

If you want to comment on these solutions, I share them in Mastodon where you can search the post for the right date or you can start a discussion over email with juhamattisantala@gmail.com.

Addendum A

It’s good to know that different programming languages deal with modulo operation differently if the value is negative.

In Python, a negative value is turned into a positive one but some languages maintain it as a negative.

For example,

mod = -20 % 100 
mod == 80

which was really handy in today’s puzzle as it took care of the wrapping of values on both sides.

Other languages like Javascript keep it negative

let mod = -20 % 100
mod === -20

This can lead to the seemingly same solution to lead to wildly different results in different languages.