Report this

What is the reason for this report?

Importing Packages in Go: A Complete Guide

Updated on March 25, 2026
English
Importing Packages in Go: A Complete Guide

Introduction

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.

Prerequisites

Install Go for your operating system:

How Packages Work in Go

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 name is the identifier in source files, such as package mathutil.
  • Import path is how the compiler locates code, such as 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.

Understanding GOPATH, GOROOT, and Go Modules

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:

  1. go env GOROOT
/usr/local/go
  1. go env GOPATH
/Users/sammy/go
  1. 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.

Using the import Keyword

Go 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.

Step 1 – Importing Standard Library Packages

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:

  1. 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:

  1. 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.

Step 2 – Using Third-Party Packages with Go Modules

Third-party imports use fully qualified module paths, and go.mod plus go.sum track versions and checksums.

Initialize a module:

  1. go mod init github.com/sammy/random
go: creating new go.mod: module github.com/sammy/random

Inspect go.mod:

  1. cat go.mod
module github.com/sammy/random

go 1.22.0

Add a dependency:

  1. 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:

  1. 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:

  1. 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:

  1. 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:

  1. go mod tidy
[no output on success]

Run go mod tidy after adding or removing imports, before committing, and in CI checks.

Step 3 – Importing Local Packages Within a Module

Local package imports inside a module use the module path prefix from go.mod, not relative paths like ./mathutil.

First, create the mathutil directory:

  1. 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:

  1. 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.

Step 4 – Importing Identically-Named Packages Using Aliases

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")
}

Comparing 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:

  1. 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

Step 5 – Using the Blank Identifier to Import for Side Effects

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.

Step 6 – Using Dot Imports

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.

Managing Dependencies with go mod

The 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.

Starting a module

Every project that uses external packages needs a go.mod file. Create one with:

  1. go mod init github.com/sammy/project
go: creating new go.mod: module github.com/sammy/project

Adding and upgrading dependencies

Use go get with a version suffix to add a new package or upgrade an existing one:

  1. 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.

Removing a dependency

Pass @none as the version to remove a package from the module graph:

  1. go get github.com/google/uuid@none
go: removed github.com/google/uuid v1.6.0

Keeping go.mod and go.sum consistent

After adding, removing, or reorganizing imports in your source files, run:

  1. 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.

Preloading the module cache

To download all dependencies to your local cache without building:

  1. 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.

Verifying the cache

To confirm that your locally cached modules have not been modified since download:

  1. go mod verify
all modules verified

Listing the full dependency graph

  1. 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

Go Workspace Mode for Multi-Module Development (Go 1.18+)

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:

  1. mkdir workspace && cd workspace
  1. mkdir myapp mylib
  1. cd myapp && go mod init github.com/sammy/myapp && cd ..
  1. cd mylib && go mod init github.com/sammy/mylib && cd ..
  1. go work init ./myapp ./mylib
[no output on success]

Verify the workspace file was created correctly:

  1. 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.

Step 7 – Using Goimports

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:

  1. go install golang.org/x/tools/cmd/goimports@latest

Check installation:

  1. goimports --help
usage: goimports [flags] [path ...]

Run a diff-only rewrite:

  1. 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:

  1. 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.

Common Import Errors

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:

  • You forgot to run go get for a third-party package.
  • The import path has a typo or incorrect capitalisation.
  • You are outside the module root (no 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 Syntax Reference Table

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)

Key Takeaways

  • A Go package is a directory of .go files with one shared package declaration.
  • Import paths identify package location, package names identify code references in source.
  • Modules (go.mod) define dependency versions, go.sum verifies integrity.
  • Local package imports use your module path prefix, not relative filesystem imports.
  • Aliases solve naming collisions and improve readability when needed.
  • Blank imports trigger side effects from init(), usually for registration patterns.
  • Dot imports reduce boilerplate but reduce clarity, keep them for narrow test scenarios.
  • Use goimports or gopls to keep imports correct and consistent.

FAQ

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.

Conclusion

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.

Learn more about our products

Tutorial Series: How To Code in Go

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.

Still looking for an answer?

Was this helpful?


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!

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.