Packages and Imports Beginner¶
Introduction¶
Every Go file belongs to a package. Packages are Go's unit of code organization, compilation, and encapsulation. The capital letter export rule controls visibility — no public/private keywords. Understanding packages, imports, and the standard library is fundamental to writing and reading any Go code.
Syntax & Usage¶
Package Declaration¶
Every Go file starts with a package statement. Files in the same directory must share the same package name.
package main // executable -- must have func main()
package user // library package -- importable by others
package user_test // test package -- can access only exported members
package main
Only package main with a func main() produces an executable binary. Everything else is a library.
Import Paths¶
// Single import
import "fmt"
// Grouped imports (preferred style)
import (
"context"
"fmt"
"strings"
"github.com/google/uuid"
"mycompany.com/myapp/internal/auth"
)
Convention: group imports in three blocks separated by blank lines:
- Standard library
- Third-party packages
- Internal/project packages
Import Variants¶
import (
"fmt" // standard import
str "strings" // alias -- use as str.Contains()
. "math" // dot import -- Sqrt() instead of math.Sqrt()
_ "github.com/lib/pq" // blank import -- side effects only (init)
_ "image/png" // registers PNG decoder
)
| Import Form | Syntax | Use Case |
|---|---|---|
| Standard | "fmt" |
Normal usage |
| Alias | str "strings" |
Name conflicts, long paths |
| Dot import | . "math" |
Avoid — pollutes namespace |
| Blank import | _ "pkg" |
Run init() for side effects only |
Avoid dot imports
Dot imports (." pkg") make it unclear where identifiers come from. The only acceptable use is in test files for frameworks like Ginkgo/Gomega. Standard Go code should never use them.
The Export Rule: Capital Letter = Public¶
package user
type User struct { // exported -- accessible outside package
Name string // exported field
email string // unexported -- lowercase, package-private
}
func NewUser() *User { // exported function (constructor)
return &User{}
}
func validate() bool { // unexported -- internal helper
return true
}
This applies to everything: types, functions, methods, struct fields, constants, variables.
// From another package:
u := user.NewUser()
u.Name = "Alice" // OK -- exported
// u.email = "..." // compile error -- unexported field
Creating Your Own Packages¶
myapp/
├── go.mod
├── main.go // package main
├── config/
│ └── config.go // package config
├── internal/
│ └── auth/
│ └── auth.go // package auth (restricted access)
└── pkg/
└── validator/
└── validator.go // package validator (public API)
// config/config.go
package config
type AppConfig struct {
Port int
DBHost string
}
func Load() (*AppConfig, error) {
// load from env/file
return &AppConfig{Port: 8080}, nil
}
// main.go
package main
import "mycompany.com/myapp/config"
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatal(err)
}
fmt.Println(cfg.Port)
}
Internal Packages¶
The internal directory restricts who can import the package. Only the parent tree can access it.
myapp/
├── internal/
│ └── auth/ // only myapp and its children can import
└── cmd/
└── server/
└── main.go // can import myapp/internal/auth
Any package outside myapp/ that tries to import myapp/internal/auth gets a compile error. The compiler enforces this — it's not just convention.
Import Cycles Are Forbidden¶
// package a imports package b
// package b imports package a
// COMPILE ERROR: import cycle not allowed
Solutions:
- Extract shared types into a third package
- Use interfaces — depend on behavior, not implementation
- Restructure — the cycle usually reveals a design problem
The init() Function¶
package mypackage
func init() {
// runs automatically when package is imported
// before main() executes
// multiple init() per file allowed (but avoid)
}
Use init() sparingly
init() runs implicitly, making code harder to test and reason about. Prefer explicit initialization in main(). Acceptable uses: registering database drivers, codecs.
go doc¶
go doc fmt # package-level docs
go doc fmt.Println # function docs
go doc -all fmt # everything in the package
Standard Library Highlights¶
| Package | Purpose | Key Functions/Types |
|---|---|---|
fmt |
Formatted I/O | Println, Printf, Sprintf, Errorf |
strings |
String manipulation | Contains, Split, Join, Replace, TrimSpace |
strconv |
String conversions | Atoi, Itoa, ParseFloat, FormatInt |
os |
OS interaction | Open, Create, Getenv, Args, Exit |
io |
I/O interfaces | Reader, Writer, Copy, ReadAll |
net/http |
HTTP client/server | Get, ListenAndServe, HandlerFunc |
encoding/json |
JSON encoding | Marshal, Unmarshal, NewEncoder, NewDecoder |
context |
Cancellation/deadlines | Background, WithTimeout, WithCancel |
sync |
Synchronization | Mutex, WaitGroup, Once, Map |
errors |
Error utilities | New, Is, As, Unwrap |
log |
Logging | Println, Fatal, SetFlags |
time |
Time and duration | Now, Since, Sleep, After, Tick |
sort |
Sorting | Strings, Ints, Slice, Search |
math |
Math functions | Max, Min, Abs, Sqrt, Pow |
regexp |
Regular expressions | Compile, MatchString, FindString |
path/filepath |
File path manipulation | Join, Base, Dir, Ext, Walk |
testing |
Test framework | T, B, Run, Errorf, Fatal |
bytes |
Byte slice operations | Buffer, Contains, Join, NewReader |
Quick Reference¶
| Concept | Rule |
|---|---|
| Executable | package main + func main() |
| Export | Uppercase first letter = exported |
| Unexported | Lowercase first letter = package-private |
| Import groups | stdlib → third-party → internal |
| Blank import | _ "pkg" — side effects only |
| Alias | alias "pkg/path" — resolve conflicts |
| Internal | internal/ directory — compiler-enforced access restriction |
| Import cycles | Forbidden — compile error |
init() |
Auto-runs on import — use sparingly |
| Unused imports | Compile error — no exceptions |
Best Practices¶
- Package names are lowercase, single-word —
usernotuserServiceoruser_service. - Don't stutter —
user.Useris fine,user.UserServicestutters. Preferuser.Service. - Keep packages focused — a package should have one clear responsibility.
- Use
internal/to prevent external packages from depending on your implementation details. - Group imports in the standard three-block format —
goimportsdoes this automatically. - Avoid
init()for anything with side effects beyond registration.
Common Pitfalls¶
Unused imports are compile errors
Go refuses to compile with unused imports. Use the blank identifier during development:
Better: usegoimports to auto-manage imports.
Circular imports
If package A imports B and B imports A, the compiler rejects it. This often means your package boundaries are wrong. Extract shared types into a separate package or use interfaces.
Unexported fields in JSON
Package name vs directory name
The package name in the source code doesn't have to match the directory name, but by convention it should. Mismatches confuse everyone.
Interview Tips¶
Interview Tip
"How does Go handle visibility/access control?" Go uses the capital letter rule: identifiers starting with an uppercase letter are exported (public), lowercase are unexported (package-private). There is no protected — the only boundary is the package. This is enforced by the compiler, not by convention.
Interview Tip
"What is a blank import and when would you use one?" A blank import (_ "pkg") imports a package solely for its init() side effects without using any of its exported names. Common examples: database drivers (_ "github.com/lib/pq") and image format decoders (_ "image/png").
Interview Tip
"How do you avoid import cycles?" Extract shared types into a separate package, define interfaces at the consumer side rather than the provider, or restructure your package hierarchy. Import cycles are a compile error in Go and often indicate a design issue.
Key Takeaways¶
- Every Go file belongs to a package;
package mainis the entry point. - Capital letter = exported, lowercase = unexported — no keywords needed.
- Import groups: stdlib, third-party, internal —
goimportsenforces this. internal/directories provide compiler-enforced encapsulation.- Import cycles are compile errors — design around them with interfaces.
- The standard library is extensive — learn
fmt,strings,os,io,net/http,encoding/json,context, andsyncfirst. - Unused imports don't compile — Go enforces clean code at the language level.