Importing packages in Go is how you reuse code from the standard library, your own module, and third-party modules. This tutorial walks through the import keyword, module-aware imports, package aliases, blank imports, dot imports, dependency management with go.mod and go.sum, and workspace mode with go.work.
You build small runnable examples in each step: generating random numbers, importing a third-party UUID package, importing a local package inside the same module, resolving package name collisions, and automating import formatting with goimports. By the end, you have a practical model for how package paths, module paths, and import syntax fit together in real Go projects.
Modules are versioned collections of packages. If you need a deeper module primer, read How To Use Go Modules.
Install Go for your operating system:
A package in Go is a directory of .go files that share the same package declaration. You import packages by import path, then reference exported identifiers from the imported package name.
The package name and import path are related, but they are not the same thing:
package mathutil.github.com/sammy/random/mathutil.When you write import "net/http", the compiler looks inside $GOROOT/src, where the Go standard library lives. When you write import "github.com/google/uuid", it looks in the module cache at $GOMODCACHE. You never manage these paths manually. Understanding the lookup order explains two common situations: why your IDE resolves standard library packages instantly without any setup, and why a third-party package causes a cannot find module providing package error until you run go get.
If a replace directive is present in go.mod, or if a go.work file is active, those paths take precedence over the module cache. This is how workspace mode lets you develop a local version of a dependency without publishing it first.
Packages live inside modules. A single module often contains many packages, including package main entrypoints and reusable library packages.
myapp/
├── go.mod
├── main.go # package main
└── mathutil/
└── mathutil.go # package mathutil
In this layout, main.go imports myapp/mathutil if module myapp appears in go.mod, or github.com/you/myapp/mathutil if the module path is fully qualified.
GOROOT points to the Go toolchain installation, GOPATH is the default workspace area for binaries and module cache, and go.mod defines module-based dependency management for your project.
GOROOT: location of the Go SDK, managed by installer or package manager.GOPATH: defaults to ~/go, used for bin/ and module cache.go.mod: per-module dependency manifest, used by modern Go development.Check each value in your environment:
- go env GOROOT
/usr/local/go
- go env GOPATH
/Users/sammy/go
- go env GOMODCACHE
/Users/sammy/go/pkg/mod
GOPATH still matters for go install destinations and module cache location, but your project does not need to live under $GOPATH/src.
import KeywordGo uses the import keyword to bring package identifiers into the current file. You can import one package per line or use a grouped import block.
Single import:
import "fmt"
Grouped imports:
import (
"fmt"
"os"
)
The compiler rejects unused imports, which keeps import blocks accurate and easy to scan.
In practice, you should always use the grouped block form even when importing a single package. The goimports tool enforces this automatically, and the Go style guide discourages chaining multiple single-line import statements. Using blocks from the start also makes it easier to add packages later without restructuring your import declaration.
Use standard library imports for common functionality such as output, random numbers, filesystem access, and HTTP clients. This first example uses math/rand.
Create random.go:
- nano random.go
Add this code:
package main
import (
"fmt"
"math/rand"
)
func main() {
for i := 0; i < 5; i++ {
fmt.Println(rand.Intn(10))
}
}
Run it:
- go run random.go
1
7
7
9
1
In older Go versions, many examples call rand.Seed(time.Now().UnixNano()). Go 1.20 changes this behavior.
In Go 1.20 and later, the global random source is automatically seeded with a random value. Calling rand.Seed() is no longer necessary and the function is deprecated. If you are on Go 1.20+, remove explicit seeding code.
If you want explicit, local random sources, use rand.New:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 5; i++ {
fmt.Println(r.Intn(10))
}
}
This pattern keeps randomness scoped to one *rand.Rand instance and avoids package-global state.
Third-party imports use fully qualified module paths, and go.mod plus go.sum track versions and checksums.
Initialize a module:
- go mod init github.com/sammy/random
go: creating new go.mod: module github.com/sammy/random
Inspect go.mod:
- cat go.mod
module github.com/sammy/random
go 1.22.0
Add a dependency:
- go get github.com/google/uuid@latest
go: downloading github.com/google/uuid v1.6.0
go: added github.com/google/uuid v1.6.0
Inspect go.mod again:
- cat go.mod
module github.com/sammy/random
go 1.22.0
require github.com/google/uuid v1.6.0 // indirect
require directives pin module versions. The // indirect suffix means the module is not imported directly by source code in the current module at that moment, but it is still required in the build graph.
Inspect go.sum:
- cat go.sum
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.sum stores cryptographic hashes for downloaded modules and their go.mod files.
Commit both go.mod and go.sum to version control. The go.sum file records cryptographic hashes of each module version and its go.mod file, which lets Go verify future downloads.
Now create uuid.go:
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
for i := 0; i < 3; i++ {
fmt.Println(uuid.New().String())
}
}
Run it:
- go run uuid.go
243811a3-ddc6-4e26-9649-060622bba2b0
b8129aa1-3803-4dae-bd9f-6ba8817f44b2
3ae27c71-caa8-4eaa-b8e6-e629b7c1cb49
When dependencies drift, clean the module graph:
- go mod tidy
[no output on success]
Run go mod tidy after adding or removing imports, before committing, and in CI checks.
Local package imports inside a module use the module path prefix from go.mod, not relative paths like ./mathutil.
First, create the mathutil directory:
- mkdir mathutil
Then create mathutil/mathutil.go:
package mathutil
// Double returns n multiplied by 2.
func Double(n int) int {
return n * 2
}
Update main.go:
package main
import (
"fmt"
"github.com/sammy/random/mathutil"
)
func main() {
fmt.Println(mathutil.Double(4))
}
Run it:
- go run main.go
8
If you see cannot find module providing package github.com/sammy/random/mathutil, confirm two things: the mathutil/ directory exists at the root of your module, and the module path in go.mod matches the prefix used in your import statement exactly.
Use aliases when two imports expose the same package name or when a long import path reduces readability.
When two packages share the same base name, assign an alias to one of them in the import block. The alias replaces the package name everywhere in that file:
package main
import (
"fmt"
"math/rand"
crand "crypto/rand"
"io"
)
func main() {
// math/rand referenced as rand
fmt.Println(rand.Intn(100))
// crypto/rand referenced as crand
b := make([]byte, 4)
_, err := io.ReadFull(crand.Reader, b)
if err != nil {
panic(err)
}
fmt.Println(b)
}
You can also alias for readability when a package name is ambiguous in context:
package main
import (
logging "log"
)
func main() {
logging.Println("application started")
}
math/rand and crypto/rand (Optional)This optional section keeps the larger side-by-side example and adds Go 1.20 guidance for crypto/rand.
Create compare.go:
package main
import (
crand "crypto/rand"
"fmt"
"io"
"math/rand"
"os"
"strconv"
"time"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage:")
fmt.Println(" go run compare.go <numberOfInts>")
return
}
n, err := strconv.Atoi(os.Args[1])
if err != nil {
panic(err)
}
b1 := make([]byte, n)
start := time.Now()
r := rand.New(rand.NewSource(start.UnixNano()))
for i := 0; i < n; i++ {
b1[i] = byte(r.Intn(256))
}
elapsed := time.Since(start)
for i := 0; i < 5 && i < len(b1); i++ {
fmt.Println(b1[i])
}
fmt.Printf("Time to generate %v pseudo-random numbers with math/rand: %v\n", n, elapsed)
b2 := make([]byte, n)
start = time.Now()
_, err = io.ReadFull(crand.Reader, b2)
elapsed = time.Since(start)
if err != nil {
panic(err)
}
for i := 0; i < 5 && i < len(b2); i++ {
fmt.Println(b2[i])
}
fmt.Printf("Time to generate %v pseudo-random numbers with crypto/rand: %v\n", n, elapsed)
}
As of Go 1.20, crand.Read() is deprecated. Use io.ReadFull(crand.Reader, b) as shown here.
Run it:
- go run compare.go 10
145
65
231
211
250
Time to generate 10 pseudo-random numbers with math/rand: 32.5µs
101
188
250
45
208
Time to generate 10 pseudo-random numbers with crypto/rand: 42.667µs
A blank import runs a package’s init() function without exposing its exported identifiers to your code.
Common use cases include SQL drivers, image decoders, and plugin registration packages.
package main
import (
"database/sql"
_ "github.com/lib/pq" // registers the PostgreSQL driver
)
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/db?sslmode=disable")
if err != nil {
panic(err)
}
_ = db.Close()
}
Without the blank import, the driver’s init() does not register "postgres" with database/sql, and sql.Open("postgres", ...) returns an unknown driver error.
Blank imports are intentional. If your linter reports an unused import, first confirm that the package registers behavior through init() before suppressing the warning.
Read more about initialization behavior in Understanding init in Go.
Dot imports copy exported identifiers from another package into the current file namespace.
Syntax:
import . "fmt"
With dot import:
package main
import . "fmt"
func main() {
Println("dot import")
}
Without dot import:
package main
import "fmt"
func main() {
fmt.Println("qualified import")
}
Avoid dot imports in production code. They hide identifier origins and can create conflicts when different packages export the same name.
The one widely accepted use case is in test files, where dot-importing the package under test removes the need to qualify every identifier. For example:
package mathutil_test
import (
. "github.com/sammy/random/mathutil"
"testing"
)
func TestDouble(t *testing.T) {
if Double(4) != 8 {
t.Errorf("expected 8, got %d", Double(4))
}
}
Without the dot import, every reference to Double would need to be written as mathutil.Double. In assertion-heavy test files, dot imports reduce that repetition. Outside of test files, the tradeoff does not hold: the readability cost of hidden identifier origins outweighs the saved keystrokes.
go modThe go mod commands manage the full lifecycle of your module’s dependencies: creating the module, adding and removing packages, and keeping go.mod and go.sum consistent with your source code.
Every project that uses external packages needs a go.mod file. Create one with:
- go mod init github.com/sammy/project
go: creating new go.mod: module github.com/sammy/project
Use go get with a version suffix to add a new package or upgrade an existing one:
- go get github.com/google/uuid@v1.6.0
go: added github.com/google/uuid v1.6.0
To pin to the latest released version, use @latest instead of a specific version tag.
Pass @none as the version to remove a package from the module graph:
- go get github.com/google/uuid@none
go: removed github.com/google/uuid v1.6.0
After adding, removing, or reorganizing imports in your source files, run:
- go mod tidy
[no output on success]
go mod tidy does two things: it adds any missing require entries for packages your code imports, and it removes require entries for packages your code no longer uses. If you remove an import from your source but forget to run go mod tidy, the stale entry stays in go.mod and bloats your dependency graph. Run it before every commit and in CI.
If go mod tidy removes a package you intended to keep, check whether that package is still imported anywhere in your source. If it is not, the removal is correct. If it is, verify that your import path matches the module path exactly, including capitalisation.
To download all dependencies to your local cache without building:
- go mod download
[no output on success]
This is useful in CI pipelines where you want to separate the download step from the build step.
To confirm that your locally cached modules have not been modified since download:
- go mod verify
all modules verified
- go list -m all
github.com/sammy/project
github.com/google/uuid v1.6.0
| Command | Purpose |
|---|---|
go mod init <module-path> |
Create a new module and go.mod |
go get <package>@<version> |
Add or upgrade a dependency |
go get <package>@none |
Remove a dependency from the module graph |
go mod tidy |
Add missing and remove unused module requirements |
go mod download |
Download dependencies to local module cache |
go mod verify |
Validate cached modules against go.sum |
go list -m all |
Print the full module dependency graph |
Workspace mode solves a specific problem: you are developing a library (mylib) and an application (myapp) that depends on it, and you want to test changes to mylib in myapp without publishing a new version of mylib first. Before workspace mode, the only way to do this was a replace directive in go.mod, which you had to remember to remove before committing. Workspace mode handles this with a separate go.work file that stays outside version control.
Set up a workspace with two modules:
- mkdir workspace && cd workspace
- mkdir myapp mylib
- cd myapp && go mod init github.com/sammy/myapp && cd ..
- cd mylib && go mod init github.com/sammy/mylib && cd ..
- go work init ./myapp ./mylib
[no output on success]
Verify the workspace file was created correctly:
- cat go.work
go 1.21
use (
./myapp
./mylib
)
To add a module to an existing workspace after initialization, run go work use ./module-path.
go.work files are typically local-development artifacts. Add go.work and go.work.sum to .gitignore unless your repository is intentionally organized as a shared multi-module workspace.
goimports formats Go source files and fixes imports in one pass. It removes unused imports and adds missing imports when it can resolve package paths.
Install it:
- go install golang.org/x/tools/cmd/goimports@latest
Check installation:
- goimports --help
usage: goimports [flags] [path ...]
Run a diff-only rewrite:
- goimports -d random.go
diff -u random.go.orig random.go
--- random.go.orig 2024-01-15 10:22:04
+++ random.go 2024-01-15 10:22:04
@@ -1,5 +1,10 @@
package main
+import (
+ "fmt"
+ "math/rand"
+)
+
func main() {
for i := 0; i < 5; i++ {
fmt.Println(rand.Intn(10))
}
}
Write changes back to file:
- goimports -w random.go
For editor-driven import management, gopls provides integrated auto-import behavior in supported IDEs. For CI pipelines and pre-commit hooks, goimports remains a reliable command-line choice.
These are the errors Go developers encounter most often when working with imports, and what each one means.
imported and not used
Go will not compile a file that imports a package without using at least one of its exported identifiers. Remove the unused import, or replace it with a blank import (_) if you need it for side effects only.
cannot find module providing package
This error means the package path in your import statement does not match any module in your build graph. The most common causes are:
go get for a third-party package.go.mod in the current directory or any parent).Run go get <package-path> to add the missing module, then verify your import path matches pkg.go.dev exactly.
package X is not in GOROOT
This error appears when you use a bare package name that the compiler expects to find in the standard library but cannot. It usually means you typed a partial import path (for example, "rand" instead of "math/rand"). Check the full import path on Go Standard Library.
ambiguous import
This happens when two modules in your dependency graph provide the same package path. It is uncommon but occurs with forked or renamed modules. Use a replace directive in go.mod to pin the version you want, or alias one of the imports to disambiguate at the call site.
// indirect stays after adding an import
After you add an import to your source code, the // indirect comment on the corresponding require line in go.mod disappears only after you run go mod tidy. Until then, Go has not re-evaluated which packages are directly imported. Run go mod tidy to update the annotation.
| Import Style | Syntax Example | Use Case |
|---|---|---|
| Standard | import "fmt" |
Import a single package |
| Block | import ( ... ) |
Group multiple packages in parentheses, one path per line |
| Alias | import mrand "math/rand" |
Resolve name conflicts or shorten long paths |
| Dot | import . "fmt" |
Promote identifiers into current namespace (test files only) |
| Blank | import _ "github.com/lib/pq" |
Side-effect-only import (driver/codec registration) |
.go files with one shared package declaration.go.mod) define dependency versions, go.sum verifies integrity.init(), usually for registration patterns.goimports or gopls to keep imports correct and consistent.Q: How do you import a package in Go?
A: Use the import keyword and specify the package path as a string. For example, import "fmt" imports the fmt package. You then reference exported members with fmt.Println.
Q: Which keyword is used to import packages in Go?
A: Go uses the import keyword. It accepts single-line imports or grouped imports in parentheses. Unused imports trigger a compiler error.
Q: How do you import multiple packages in Go?
A: Use an import block with parentheses. List each path on its own line:
import (
"fmt"
"os"
)
Tools like goimports keep this block sorted and consistent.
Q: How do packages work in Go?
A: A package is a directory of source files sharing the same package clause. Other files import that package by import path, and only exported identifiers are accessible. Packages are versioned and distributed through modules.
Q: What is the difference between GOPATH and Go modules?
A: GOPATH is a workspace location that still hosts installed binaries and module cache. Go modules are the dependency system used by current Go development, based on go.mod and go.sum. Your project no longer depends on being inside GOPATH/src.
Q: What does a blank identifier import do in Go?
A: A blank import (_ "pkg/path") imports a package only for side effects. It runs init() and discards direct identifier access. This pattern is common with SQL drivers and codec registration packages.
Q: When should you use a package alias in Go?
A: Use aliases when two packages have the same name, or when a path is long and clarity improves with a short local name. Good aliases stay descriptive, such as crand for crypto/rand. Avoid aliases that obscure package purpose.
Q: What is the difference between go get and go install?
A: go get adds or updates a package in your current module’s go.mod and makes it available to import in your code. go install compiles and installs an executable binary to $GOPATH/bin. Use go get when you want to use a package as a library dependency. Use go install when you want to install a command-line tool, such as goimports or staticcheck.
Q: How do I know whether a package is in the standard library or third-party?
A: Standard library packages have short, unqualified import paths with no domain prefix, such as fmt, os, net/http, or crypto/rand. Third-party packages always include a domain, such as github.com/google/uuid. Browse the full standard library at pkg.go.dev/std. Use go doc <package> in your terminal to read documentation for any package already in your module graph.
You now have a practical import workflow for standard library packages, module dependencies, local packages, aliases, blank imports, dot imports, and workspace-mode development. These patterns cover day-to-day package management in modern Go projects.
Continue with How To Write Packages in Go, then revisit How To Use Go Modules for deeper dependency workflows. For related topics, read Understanding init in Go, How To Use the Flag Package in Go, and How To Build Go Executables for Multiple Platforms on Ubuntu.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Go (or GoLang) is a modern programming language originally developed by Google that uses high-level syntax similar to scripting languages. It is popular for its minimal syntax and innovative handling of concurrency, as well as for the tools it provides for building native binaries on foreign platforms.
Browse Series: 53 tutorials
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.