When I started learning Go, one of the first questions I had was:
“How do I actually structure my code?”
In languages like C, it’s common to throw everything into a single file, or maybe separate header and implementation files. But in Go, project structure is a big deal: it affects how easily you can scale, test, and share your code. In this article, I’ll walk through why structure matters, how to access functions from different files, and what best practices I’m learning as I move forward.
The Simplest Go Program
A Go program can live entirely in a single file:
package main
import "fmt"
func main() {
fmt.Println("2 + 2 =", 2+2)
}
This works fine for “hello world” or quick experiments.
But as soon as you add more functionality (subtraction, multiplication, division…), the file gets messy and hardly scalable.
That’s where Go’s packages and folders come in.
Introducing Packages
In Go, every file belongs to a package.
By convention:
- main → executable program
- others (like calculator, utils, etc.) → reusable logic
Here’s how I split the calculator project:
calculator/
│
├── main.go
└── calculator/
└── operations.go
- main.go → handles input/output
- operations.go → defines functions like Add, Subtract, etc.
Example: operations.go
package calculator
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
func Multiply(a, b int) int {
return a * b
}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
Example: main.go
package main
import (
"fmt"
"GoLang-progress/calculator"
)
func main() {
fmt.Println("2 + 3 =", calculator.Add(2, 3))
fmt.Println("10 - 4 =", calculator.Subtract(10, 4))
fmt.Println("6 * 7 =", calculator.Multiply(6, 7))
result, err := calculator.Divide(8, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("8 / 0 =", result)
}
}
Notice how main.go is now clean: it doesn’t worry about the math itself, just how to use it.
Accessing Functions from Different Files
A common beginner question:
“How do I call a function from another file or folder?”
In my repo, I structured it like this:
calculator/
│
├── main.go
└── internal/
└── calc/
└── operations.go
Here, the math functions live under internal/calc.
operations.go (inside internal/calc)
package calc
import "fmt"
func Add(a, b int) int {
return a + b
}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
main.go (importing internal/calc)
package main
import (
"fmt"
"github.com/turman17/GoLang-progress/calculator/internal/calc"
)
func main() {
fmt.Println("2 + 3 =", calc.Add(2, 3))
result, err := calc.Divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("10 / 2 =", result)
}
}
Why this import path is required
Your import must match your module path from go.mod plus the folder path.
In your repo, go.mod contains:
module github.com/turman17/GoLang-progress
The calculator code you want to use lives in the folder:
calculator/internal/calc
So the full import path is:
github.com/turman17/GoLang-progress/calculator/internal/calc
A few important notes
- Folder name ≠ package name → The folder is internal/calc, but the package inside is declared as package calc.
- Imports use module path → Always start with github.com/... if that’s in your go.mod.
- Internal is special → Packages under /internal can only be imported by code inside the same module.
Common errors and fixes
❌ import "GoLang-progress/calculator/internal/calc"
→ Missing GitHub org/username. Must use full path.
❌ import "github.com/turman17/GoLang-progress/internal/calc"
→ Missing calculator directory in the path.
❌ go: module not found errors
→ Ensure go.mod has module github.com/turman17/GoLang-progress and run go mod tidy.
Quick checklist
- go.mod has the correct module line
- Directory is calculator/internal/calc with package calc inside
- main.go imports github.com/turman17/GoLang-progress/calculator/internal/calc
- Build from the module root:
go run ./calculator
or
go build ./calculator
Scaling the Structure
As projects grow, you’ll often see this pattern:
project-name/
│
├── cmd/ → executables (main entrypoints)
├── internal/ → private code (not for external use)
├── pkg/ → reusable packages
├── api/ → API definitions (gRPC, OpenAPI, etc.)
└── go.mod
For beginners, this might be overkill. But as you move into web apps, services, or MLOps tools, this layout becomes essential.
Best Practices I’m Learning
- Keep packages small and focused
- Use meaningful names (calc, parser, storage)
- Don’t over-engineer — start simple, refactor later
- Avoid circular dependencies (Go enforces this)
Lessons from the Calculator Project
- Separating logic (operations.go) from entrypoint (main.go) makes testing easier.
- Error handling (like divide by zero) should be explicit.
- Import paths really matter — especially when using internal.
I’ll continue sharing what I learn as I explore Go for MLOps and backend development. Next up: error handling and testing in Go.
👉 Check out my repo here: https://github.com/turman17/GoLang-progress
And stay tuned for the next article!