Back to Blog

The Go import Keyword: Syntax, Aliases, Blank and Dot Imports

How Go's import keyword works: grouped imports, import paths, why unused imports fail the build, aliases, blank and dot imports, and fixing import cycles.

The Go import Keyword: Syntax, Aliases, Blank and Dot Imports

The import keyword makes another package's exported names available in a Go source file. Imports come right after the package clause and before any other declaration, and each one names a package by its import path, such as "net/http" or "github.com/jackc/pgx/v5". You then reach that package's contents through its name, as in http.ListenAndServe. An import can also carry an alias, a blank identifier _ or a dot ., and each form changes how the package's name is bound in the file (Go spec).

TL;DR

  • Imports go after package and before everything else. Most files use one grouped import ( ... ) block.
  • An import path is a string. Standard library paths are short ("encoding/json"), and everything else starts with a module path from go.mod.
  • An unused import is a compile error: "os" imported and not used. goimports and gopls add and remove imports for you.
  • alias "path" renames a package inside one file. Use it when two imports share a name, like html/template and text/template.
  • _ "path" imports a package only for its side effects, which run in its init functions. Database drivers and net/http/pprof work this way.
  • . "path" pulls names into the file without a qualifier. Style guides discourage it and staticcheck flags it as ST1001, apart from rare test cases.
  • Go refuses import cycles. The fix is a new shared package or an interface defined where it is used.
  • Imports are file-scoped. Every file in a package imports what it uses, even when a sibling file already imports the same package.

How do you import a package in Go?

Write import followed by the package's path in double quotes. A file that needs one package can use a single line, and a file that needs several uses a grouped block with parentheses:

example.gogo
package main

import "fmt"

func main() {
	fmt.Println("ok")
}
example.gogo
package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type healthResponse struct {
	Status string `json:"status"`
}

func main() {
	http.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(healthResponse{Status: "ok"})
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Both forms mean the same thing, and almost all Go code uses the grouped block. Imports always come after the package clause and before any const, var, type or func. Put one lower down and the parser stops with syntax error: imports must appear before other declarations. There is no way to import inside a function either.

Formatting tools order the block for you. gofmt sorts the lines within each group by import path. goimports, which most editors run on save, also splits the block into two groups separated by a blank line, with the standard library first and everything else second:

example.gogo
import (
	"context"
	"fmt"
	"net/http"

	"example.com/shop/billing"
	"github.com/jackc/pgx/v5"
)

The order has no effect on the program. It keeps diffs small and shows readers at a glance which dependencies come from outside the standard library.

What is an import path in Go?

An import path is the string that tells the go command where a package lives. Standard library packages have short paths with no dot in the first element, like "fmt", "net/http" or "crypto/rand". Every other package's path starts with a module path.

Your own packages use the module path declared in go.mod plus the directory. With module example.com/shop in go.mod, the package in ./billing is imported as "example.com/shop/billing". There are no relative imports like "./billing" in module mode, so a local package is always imported by its full path.

Third-party packages work the same way, and the go command resolves them through go.mod:

  1. go get github.com/jackc/pgx/v5 downloads the module and adds a require line to go.mod.
  2. You write "github.com/jackc/pgx/v5" in the import block.
  3. go mod tidy later adds any module your imports need and removes any that nothing imports anymore.

The last element of the path is usually the package name, but not always. "github.com/jackc/pgx/v5" provides a package called pgx, and "gopkg.in/yaml.v3" provides yaml. The Go package keyword explains how names and paths relate.

Why does Go refuse to compile unused imports?

Go treats an unused import as an error, not a warning. Leave "os" in a file that no longer calls it and go build stops:

example.texttext
./main.go:5:2: "os" imported and not used

The Go FAQ gives the reasoning. An unused import slows compilation and adds a dependency the program doesn't need, and in a large codebase those pile up. People ignore warnings, so Go makes it an error instead. go mod tidy does the same cleanup for go.mod, removing requirements that nothing imports.

In practice you rarely fix these by hand. goimports and gopls, the Go language server behind VS Code and GoLand's Go support, delete unused imports and add missing ones when you save. While you are debugging and want to keep an import around for a minute, assign something from it to the blank identifier, as in var _ = os.Exit, and remove the line before you commit.

How do you rename an import in Go?

Put an alias before the path. The alias replaces the package name for that file only:

example.gogo
import htmltemplate "html/template"

The usual reason is a name clash. html/template and text/template are both called template, and an email service often needs both: plain text for the subject line and HTML-escaped output for the body. Importing both without an alias fails with template redeclared in this block. An alias on one of them fixes it:

example.gogo
package main

import (
	htmltemplate "html/template"
	"os"
	"text/template"
)

func main() {
	subject := template.Must(template.New("subject").Parse("Your invoice {{.ID}}\n"))
	body := htmltemplate.Must(htmltemplate.New("body").Parse("<p>{{.Note}}</p>\n"))

	data := map[string]string{"ID": "INV-42", "Note": "<script>alert(1)</script>"}
	subject.Execute(os.Stdout, data)
	body.Execute(os.Stdout, data)
}

The output shows why the two packages exist separately. The subject prints as Your invoice INV-42, and the body prints as <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>, escaped by html/template.

Aliases also show up where many packages share a generic last element. Kubernetes code imports several packages that are all named v1, so it writes corev1 "k8s.io/api/core/v1" and metav1 "k8s.io/apimachinery/pkg/apis/meta/v1". When there is no clash, keep the real name. Everyone recognizes http., but a custom alias sends the reader back to the import block to find out what it means.

What does a blank import _ do in Go?

A blank import loads a package without binding its name. You can't call anything from it, but the package still gets initialized, so its package-level variables are set and its init functions run. Some packages do their useful work in init by registering themselves with another package.

The most common example is a database/sql driver:

example.gogo
package main

import (
	"database/sql"
	"log"
	"os"

	_ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
	db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
}

The pgx stdlib package calls sql.Register("pgx", ...) in its init. Your code only talks to database/sql, but without the blank import the driver never registers and sql.Open returns sql: unknown driver "pgx" (forgotten import?).

Other blank imports you will meet in real services:

ImportWhat its init does
_ "github.com/jackc/pgx/v5/stdlib"Registers the pgx driver with database/sql
_ "net/http/pprof"Adds /debug/pprof/ profiling handlers to http.DefaultServeMux
_ "image/png"Registers the PNG decoder so image.Decode can read PNG files
_ "time/tzdata"Embeds the time zone database for containers that have none
_ "embed"Nothing at runtime. It is required before //go:embed works on a string or []byte variable

The embed case is a toolchain rule rather than an init side effect. Leave the import out and the build fails with go:embed requires import "embed" (or import _ "embed", if package is not used).

Initialization order is predictable. Each imported package is fully initialized, including its own imports, before the package that imports it. Since Go 1.21 the spec also fixes the order between unrelated packages: they are initialized in import path order. Within one package, package-level variables are set first and then init functions run in the order the files are presented to the compiler, which the go command sorts by file name. main runs last. A package imported by several others is still initialized only once.

Put blank imports in main or in the package that actually needs the side effect, not in a library that other people import. A library that blank-imports net/http/pprof exposes profiling endpoints in every program that uses it, whether the program wants them or not.

Should you use dot imports in Go?

Almost never. A dot import merges a package's exported names into the file, so you call them without the package name:

example.gogo
import . "strings"

func normalizeEmail(email string) string {
	return ToLower(TrimSpace(email))
}

In a three-line function that is easy to follow. In a long file, a reader who sees ToLower can't tell whether it is defined in this package or which import it came from. The Go Code Review Comments page recommends against it, and staticcheck flags it:

example.texttext
main.go:3:8: should not use dot imports (ST1001)

The one accepted use is a test that has to live outside the package it tests because of a cycle. Code Review Comments gives the example of package foo_test importing bar/testutil, which itself imports foo. A dot import of foo lets that test read as if it were inside the package. Some test frameworks, such as Ginkgo and Gomega, also document dot imports for their matchers. Outside those cases, write the package name.

What is an import cycle in Go?

An import cycle is two or more packages that import each other, directly or through a chain. Go forbids them, so billing importing customers while customers imports billing fails at build time:

example.texttext
package example.com/shop/billing
	imports example.com/shop/customers from billing.go
	imports example.com/shop/billing from customers.go: import cycle not allowed

The rule keeps initialization order well defined and builds fast, because the compiler can always compile a package after everything it depends on. It also tells you something about the design: a cycle usually means two packages are really one, or that both depend on something that belongs in a third.

There are two common fixes:

  1. Move the shared part into its own package. If billing and customers both need a CustomerID type, put it in a small shop/ids or shop/domain package that imports neither.
  2. Define an interface where it is used. If billing only needs to look up a customer's email, it can declare what it needs and let main pass in the real implementation:
example.gogo
package billing

import "context"

// CustomerLookup is the one thing billing needs from the customers package.
type CustomerLookup interface {
	Email(ctx context.Context, customerID string) (string, error)
}

type Service struct {
	customers CustomerLookup
}

func NewService(customers CustomerLookup) *Service {
	return &Service{customers: customers}
}

Now billing no longer imports customers, and customers can import billing freely. Any type with a matching Email method satisfies the interface without naming it, so the dependency only points one way.

The go command applies a related rule to package main. No package can import it, and trying gives import "example.com/shop/cmd/api" is a program, not an importable package.

Are Go imports file-scoped or package-scoped?

Imports are file-scoped. An import only binds the package name in the file that contains it, even though every other package-level name is shared across all files in the package. Here server.go imports log:

example.gogo
package main

import "log"

func main() {
	log.Println("server starting")
	runJobs()
}

And jobs.go in the same package calls log without importing it:

example.gogo
package main

func runJobs() {
	log.Println("running jobs")
}

runJobs is visible in server.go because package-level functions are shared. The log import is not, so the build fails:

example.texttext
./jobs.go:4:2: undefined: log

Each file lists its own imports, and goimports adds them for you. It also means an import name can clash with a package-level declaration in another file. Declare var log = ... in one file while another file imports log and you get log already declared through import of package log ("log").

Aliases are file-scoped too. Renaming mrand "math/rand/v2" in one file has no effect on the other files in the package.

Go also restricts which packages you can import at all. A package under an internal/ directory can only be imported by code rooted at the parent of internal. The Go package keyword covers internal/ and how visibility works across packages.

Where LevelUpGo fits

LevelUpGo teaches Go through exercises that run real Go code in the browser. The Packages & Organization course covers exported names, init and package state, Go modules and dependencies, and the module commands you run every day, like go get and go mod tidy. If you are just starting, Go Basics comes first. For the other 24 reserved words, see Go keywords: all 25 explained.

FAQ

Is import a keyword in Go?

Yes. import is one of Go's 25 reserved keywords, so you cannot use it as a variable, function or package name. It can only appear in import declarations at the top of a file.

Can you import a package inside a function in Go?

No. Imports are only allowed at the top level of a file, right after the package clause. An import inside a function body fails with syntax error: unexpected keyword import. If you only need a package in one function, import it at the top of the file anyway.

How do you import a local package in Go?

Use the module path from go.mod followed by the package's directory. In a module named example.com/shop, the package in ./internal/pricing is imported as "example.com/shop/internal/pricing". Go modules don't support relative imports such as "./pricing".

What is the difference between import _ and import . in Go?

import _ "path" runs the package's initialization but gives you no way to refer to it. It is for side effects like registering a database driver. import . "path" does the opposite and puts all of the package's exported names directly into your file, so you can call them without a qualifier. Blank imports are common in idiomatic Go, while dot imports are discouraged.

Does the order of imports matter in Go?

No. The compiler doesn't care about the order of lines in an import block, and initialization order is determined by the dependency graph, not by how you list the imports. gofmt sorts them by path and goimports separates the standard library from other packages, but that is only for readability.

Sources

Write Go like a senior engineer

Interactive lessons in your browser. The first ones are free.

Try a free lessonOr create a free account