Advent of Code 2025 in GO - Day 1 - Part #1

Thumbnail for Advent of Code 2025 in GO

Why am I releasing Advent of Code 2025 articles in 2026? Recently, I transitioned into a new role as a Platform Engineer, where Go has become my primary programming language. After diving into several YouTube crash courses, a colleague suggested a more hands-on approach: tackling Advent of Code. It’s the perfect way to force myself out of my comfort zone, master Go’s syntax, and sharpen my algorithmic thinking. I’ve decided to document this process and share my Go learning journey with all of you.


In case you prefer learning with a video, checkout my YouTube video 📺

Habibi Coding Video - Debian VS Ubuntu


Step 1: Open Advent of Code

Follow these steps to join the challenge:
1. Navigate to Advent of Code 2025.
2. Click [Log In] at the top and select an OAuth service (e.g., GitHub or Google).
3. Choose your username when prompted and click [Save].
4. On the main calendar page, click the number 1 to start the first puzzle.

Day 1: Secret Entrance

A better overview of the instructions you find here: Day 1: Secret Entrance


Step 2: Open your IDE and start coding

Start by initializing a new Go project for example: advent-of-code-2025. It is a good practice to include a README.md file for important steps and notes, which you can update as you progress.

Next, set up your folder structure by creating internal/util/util.go. This utility file will handle reading puzzle instructions from your text files. Since each day typically has two parts, we will save the puzzle data as input1.txt and input2.txt for consistency.

Your code in util.go should look like this:

package util

import (
	"os"
	"strings"
)

func ReadInput(path string) (string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(data)), nil
}

Unit Testing

It's time for some unit testing! Something I really appreciate about Go is how easy it makes testing your code. To get started, create a testdata directory within util. Inside, create a file called test_input.txt and paste in this data:

With test_input.txt in place, we can now verify our logic. Create a new file in the util directory named util_test.go.
We will implement func TestReadInput(t *testing.T) using Go's "table-driven tests" pattern. This involves defining a slice of test cases, each with its own inputs and expected outputs and iterating through them to validate our function. It’s a clean, idiomatic way to handle multiple scenarios in a single test function.

func TestReadInput(t *testing.T) {
	tests := []struct {
		name    string
		path    string
		want    string
		wantErr bool
	}{
		{
			name:    "successful read of data",
			path:    "testdata/test_input.txt",
			want:    "L68\nL30\nR48\nL5\nR60\nL55\nL1\nL99\nR14\nL82",
			wantErr: false,
		},
		{
			name:    "file not found",
			path:    "testdata/non-existent-file.txt",
			want:    "",
			wantErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got, err := ReadInput(test.path)
			if (err != nil) != test.wantErr {
				t.Errorf("ReadInput() error = %v, wantErr %v", err, test.wantErr)
				return
			}
			if got != test.want {
				t.Errorf("ReadInput() = %q, want %q", got, test.want)
			}
		})
	}
}

Now, if you run go test ./... from your Terminal in the root directory you should see: ok advent-of-code-2025-blog/internal/util 0.502s

🧮 Start to implement the algo

Create from the root directory following folder structure: cmd/day01 put your main.go file inside. This will later only hold our data to show in the Terminal. That's why we need a second file for the business logic, create the file secret_entrance.go and start adding these constants and struct first: From the root directory, create the following path: cmd/day01. Place your main.go file here, this will serve as the entry point to output our results to the terminal.
To maintain a clean separation of concerns, we will keep our business logic in a separate file. Create secret_entrance.go in the same folder and start by adding these constants and struct:

const zeroPos = 0
const lastPos = 99
const allHundredPos = 100
const moveOneStep = 1

type Rotator struct {
	direction string
	steps     int
}

Next, we need a function which transforms our input1.txt to a slice of Rotator, so add this function:

func generateInstructions(input string) []Rotator {
	instructions := make([]Rotator, 0, 100)
	regex := regexp.MustCompile(`([A-Z])(\d+)`)
	lines := strings.Split(input, "\n")
	const charIdx = 1
	const stepsIdx = 2
	for _, line := range lines {
		matches := regex.FindStringSubmatch(line)
		if matches != nil {
			letter := matches[charIdx]
			amount, _ := strconv.Atoi(matches[stepsIdx])
			instructions = append(instructions, Rotator{letter, amount})
		}
	}
	return instructions
}

This utility function acts as a "parser." It reads the puzzle input line by line, identifies the specific letters and numbers we need, and converts them into a Go-friendly format []Rotator so we don't have to deal with messy strings later in the program.

The final method to implement for Part 1

Now, we need the function which counts every time when the dial points exactly to zero:

func countDialPointsZero(rotations []Rotator) int {
	dialPos := 50
	counter := 0
	for _, rotator := range rotations {
		twoDigitNum := rotator.steps % allHundredPos
		if rotator.direction == "R" {
			if (twoDigitNum + dialPos) > lastPos {
				dialPos = (dialPos + twoDigitNum) - moveOneStep
				dialPos = dialPos - lastPos
			} else {
				dialPos += twoDigitNum
			}
		} else {
			if (dialPos - twoDigitNum) < zeroPos {
				dialPos = (dialPos - twoDigitNum) + moveOneStep
				dialPos = lastPos + dialPos
			} else {
				dialPos -= twoDigitNum
			}
		}
		if dialPos == zeroPos {
			counter++
		}
	}
	return counter
}

This function simulates the movement of a circular dial based on a sequence of rotations. Starting from position 50, it processes each instruction to move the dial left or right, carefully handling the "wrap-around" logic when the dial passes the boundary. Finally, it counts and returns the total number of times the dial landed exactly on the zero position.

Check if the solution for part 1 works

Now create the in the same folder the file secret_entrance_test.go and add the following code:

package main

import (
	"reflect"
	"testing"
)

func TestGenerateInstructions(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  []Rotator
	}{
		{
			name:  "single instruction R",
			input: "R12",
			want:  []Rotator{{"R", 12}},
		},
		{
			name:  "single instruction L",
			input: "L99",
			want:  []Rotator{{"L", 99}},
		},
		{
			name:  "multiple instructions",
			input: "R12\nL99\nR48",
			want: []Rotator{
				{"R", 12},
				{"L", 99},
				{"R", 48},
			},
		},
		{
			name:  "empty input",
			input: "",
			want:  []Rotator{},
		},
		{
			name:  "input with extra whitespace and empty lines",
			input: "R12\n\nL99\n  ",
			want: []Rotator{
				{"R", 12},
				{"L", 99},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got := generateInstructions(test.input)
			if !reflect.DeepEqual(got, test.want) {
				t.Errorf("generateInstructions(%q)\ngot  = %v\nwant = %v", test.input, got, test.want)
			}
		})
	}
}

This Unit Test as the name implies is for testing the function generateInstructions(), this style of testing is called table-driven test in Go. It’s the idiomatic way to write tests in the Go ecosystem because it keeps your logic "DRY" (Don't Repeat Yourself) while making it incredibly easy to add new edge cases just by adding a line to your slice. Now add the second test function:

func TestCountDialPointsZero(t *testing.T) {
	tests := []struct {
		name  string
		input []Rotator
		want  int
	}{
		{
			name: "no zero hits",
			input: []Rotator{
				{"R", 10},
				{"L", 5},
			},
			want: 0,
		},
		{
			name: "hit zero exactly moving right",
			input: []Rotator{
				{"R", 50},
			},
			want: 1,
		},
		{
			name: "hit zero moving left",
			input: []Rotator{
				{"L", 50},
			},
			want: 1,
		},
		{
			name: "multiple hits and moves",
			input: []Rotator{
				{"R", 50},
				{"L", 50},
				{"L", 50},
			},
			want: 2,
		},
		{
			name: "large steps wrapping around multiple times",
			input: []Rotator{
				{"R", 150},
			},
			want: 1,
		},
		{
			name: "complex sequence wrapping around",
			input: []Rotator{
				{"R", 70},  // 50 + 70 = 120 -> 120-1-99 = 20
				{"L", 40},  // 20 - 40 = -20 -> -20+1+99 = 80
				{"R", 20},  // 80 + 20 = 100 -> 100-1-99 = 0 (hit 1)
				{"R", 100}, // 0 + 100%100 (0) = 0 (hit 2)
			},
			want: 2,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got := countDialPointsZero(test.input)
			if got != test.want {
				t.Errorf("%s: countDialPointsZero() = %d; want %d", test.name, got, test.want)
			}
		})
	}
}

Here we use again table-driven test with some test scenarios to verify that our logic works and that's it for the first part for challenge day 1.

Next copy the input of "To begin, get your puzzle input." in separate text file called input1.text in the same level as main.go Then open main.go copy-paste that code:

package main

import (
	"fmt"
	"log"
)

func main() {
	input, err := util.ReadInput("../advent-of-code-2025/cmd/day01/input1.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("The password for Part1 is: ", countDialPointsZero(generateInstructions(input)))
}

Copy and paste the terminal result for Part 1 into your Advent of Code input. Since everyone receives a unique input, your result will differ from mine. For this reason, I won’t display my specific result here, just follow the same logic to arrive at your own solution.

Conclusion

This Go implementation for day 1 part 1 efficiently parses string-based movement commands into structured data to simulate a 100-position circular dial. By combining regex-based input sanitization with manual wraparound logic, it accurately tracks "zero-point" hits—providing a robust, idiomatic pattern for handling mechanical simulations and directional logic.

Here is the link to Part 2

Here is the link to the GitHub repo



Frequently Asked Questions

Find answers to common questions about Go

What is Go (Golang) and why was it created?

Go, also known as Golang, is an open-source programming language developed by Google engineers in 2007. It was created to address the challenges of modern software development, such as slow compilation, uncontrolled dependencies, and the difficulty of writing programs that can easily scale in distributed environments. Go emphasizes simplicity, efficiency, and readability.

How does Go handle concurrency?

Go uses goroutines and channels to handle concurrency. Goroutines are lightweight threads managed by the Go runtime, allowing you to run multiple tasks concurrently with minimal overhead. Channels are used to communicate between goroutines, ensuring safe data exchange and synchronization.

What are the main features of Go?

Go is known for its simplicity, fast compilation, garbage collection, strong standard library, and built-in concurrency support. It also features static typing, interfaces, and a focus on backward compatibility. These features make Go ideal for building scalable and maintainable software.

How do you manage dependencies in Go?

Go uses a module system for dependency management. You define your project's dependencies in a go.mod file, and Go automatically downloads and manages the required packages. The go get and go mod tidy commands are commonly used to add and clean up dependencies.

What is the difference between := and = in Go?

In Go, := is the short variable declaration operator, which declares and initializes a variable in one step (e.g., x := 10). The = operator is used for assignment to already declared variables (e.g., x = 20). The short declaration can only be used inside functions.

Want to buy me a coffee?