The first puzzle of 2025's AoC (available at https://adventofcode.com/2025/day/1) introduces us to a story where elves have discovered project management. Good for them. Now we need to help open a safe — time to break some law.
The safe has a dial that starts at position 50, and we need to count the number of times the dial points at zero after processing a sequence of rotations. The dial has 100 positions (0-99) and wraps around.
Parsing the Input
The input consists of instructions in the format L68 or R30, where:
Rmeans rotate right (toward higher numbers: 1, 2, 3, 4...)Lmeans rotate left (toward lower numbers: 49, 48... 1, 0, 99, 98...)
As always, I start by defining proper types for the data:
type Instruction = {
direction: "R" | "L";
steps: number;
};
The parsing is straightforward - extract the first character as the direction and parse the rest as a number:
const processInput = (day: number): Instruction[] => {
const lines = readInputLines(day);
const instructions = lines.map(line => {
const direction = line.charAt(0) as "R" | "L";
const steps = parseInt(line.slice(1), 10);
return { direction, steps };
});
return instructions;
};
Part One: Counting Zero Landings
For part one, we need to count how many times the dial lands exactly on zero after applying each instruction. The naive approach would be:
let position = 50;
let result = 0;
for (const instruction of input) {
if (instruction.direction === "R") {
position += instruction.steps;
} else {
position -= instruction.steps;
}
position = position % 100;
if (position === 0) {
result += 1;
}
}
But this doesn't work. Why? Because JavaScript's modulo operator behaves differently than in some other languages when dealing with negative numbers:
-1 % 100 // returns -1, not 99
-300 % 100 // returns -0 (yes, negative zero is a thing in JavaScript!)
The toLock Function
We need a proper modulo function that always returns a value between 0 and 99:
const toLock = (n: number) => (n % 100 + 100) % 100;
This works by:
- First applying
% 100, which gives us a number between -99 and +99 - Adding 100 to guarantee a positive number (between 1 and 199)
- Applying
% 100again to get back to the 0-99 range
With this fix, part one returns 141 for my input.
Part Two: Counting Zero Crossings
Part two changes the requirement - now we need to count every time the dial passes through zero during rotation, not just when it lands on zero at the end.
For example, starting at position 50 and rotating left 68 steps means we pass through zero once on our way to position 82.
The mathematically elegant solution would be to calculate how many times we cross zero based on the start position, end position, and direction. But I chose the simpler (and admittedly less efficient) approach: simulate every single click.
let position = 50;
let result = 0;
for (const instruction of input) {
if (instruction.direction === "R") {
let clicks = instruction.steps;
while (clicks > 0) {
position += 1;
position = toLock(position);
if (position === 0) {
result += 1;
}
clicks -= 1;
}
} else {
let clicks = instruction.steps;
while (clicks > 0) {
position -= 1;
position = toLock(position);
if (position === 0) {
result += 1;
}
clicks -= 1;
}
}
}
Performance Considerations
This simulation approach is wasteful. If we rotate 200 times, we're doing 200 loop iterations when we could just calculate that we pass zero twice (200 / 100 = 2).
However, a quick analysis of the input shows:
- Maximum steps per instruction: 999
- Total instructions: ~4,500
That's a few million iterations at worst - child's play for modern hardware. Part two runs in about 14 milliseconds compared to 1 millisecond for part one.
The answer for part two is 6,634.
Takeaways
Know your language's quirks: JavaScript's modulo behavior with negative numbers catches many developers off guard. The pattern
(n % m + m) % mis a common fix.Simple solutions are often good enough: The brute-force simulation for part two is inefficient but perfectly adequate. Premature optimization is the root of all evil, and this code is readable and obviously correct.
Type your data early: Defining the
Instructiontype immediately makes the rest of the code cleaner and catches errors at compile time rather than runtime.
The full solution is available in the repository.