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.
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.
A better overview of the instructions you find here: Day 1: Secret Entrance
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
}
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
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.
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.
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.
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
Find answers to common questions about Go
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.
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.
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.
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.
:= 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.