Skip to content
gohacks.dev
Basics

Undefined Name Errors in Go and Where They Come From

undefined means the compiler cannot find a name in scope. Check typos, imports, scope, unexported names, files left out of the build and build tags.

6 min read
Summarize in AI
Flat illustration of a log with a failing line and a wrench.

undefined: something is the compiler's way of saying it looked everywhere it was allowed to look and found nothing by that name. It is a short message with at least eight distinct causes, and the order in which you check them matters, because the first three account for most of the cases. This article lists them in that order, with the actual output, and ends with the build-system causes that fool people who are certain the function exists. It is related to declared and not used, which often appears beside it, and to packages and exported names, which explains the visibility rule behind one of the causes. It belongs in the Go basics path.

Tested with Go 1.25.6, macOS on Apple Silicon (arm64)

The first four causes, in one file

This file has a typo, a missing import, a wrong scope and a name that was never declared:

main.goGo
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(helper(2))
	fmt.Println(Count)
	fmt.Println(strings.Toupper("x"))
	fmt.Println(fmt.println("y"))
	for i := 0; i < 2; i++ {
		total := i
	}
	fmt.Println(total)
	fmt.Println(os.Args)
	var s Server
	s.Start()
}
Text
# example.test/lab
./main.go:9:14: undefined: helper
./main.go:10:14: undefined: Count
./main.go:11:22: undefined: strings.Toupper (but have ToUpper)
./main.go:12:18: undefined: fmt.println (but have Println)
./main.go:14:3: declared and not used: total
./main.go:16:14: undefined: total
./main.go:17:14: undefined: os
./main.go:18:8: undefined: Server

Each error names the first thing the compiler could not resolve. Reading the output top to bottom gives the checklist.

Nothing by that name exists. undefined: helper, undefined: Count, undefined: Server. The name is not declared in this function, in this package's files that are part of the build, or in the universe of builtins. Either it was never written, it was misspelled, or it lives where the compiler is not looking, which the rest of this article covers.

A case typo. undefined: strings.Toupper (but have ToUpper). Modern compilers search for a name that differs only in capitalization and tell you. The same for fmt.println, which exists as Println and must start with a capital to be reachable from another package. If the hint is there, believe it.

A scope mistake. total was declared inside the for body with :=, so after the closing brace it does not exist, and the next line reports undefined: total. The compiler also complained that total was declared and not used, which is the same mistake seen from the inside. The fix is to declare the variable before the loop, total := 0, and assign inside it with =.

A missing import. undefined: os means the code uses os.Args and no os import is present. This is the one editors fix automatically: goimports or gopls adds the import on save. In a bare editor the fix is a line in the import block. The reverse mistake, an import you stop using, is a different error, as the hello world article shows.

Unexported names in other packages

Calling something that exists, but is private to its package, gives a variant of the same family. For a function that starts with a lowercase letter, the message is undefined: pkg.name, or, depending on the situation, name not exported by package, both described in packages and exported names. The two-line fix is to export the name (capitalize it) or to add an exported wrapper. If the name is in someone else's module, it is private on purpose, and you need a different API.

A related message is x.y undefined (type T has no field or method y). That one says the identifier is fine and the type does not have the member. Check the spelling and capitalization again, then check whether the method has a pointer receiver and the variable is a value of an interface type that does not include it.

The file you did not compile

The cause that wastes the most time is a function that is defined, in a file the build does not include. Two causes:

go run main.go. With a file name argument, the command compiles only the files you list. A function in a sibling file is invisible:

main.goGo
package main

import "fmt"

func main() {
	fmt.Println(other())
}
Text
# command-line-arguments
./main.go:6:14: undefined: other
util.goGo
package main

func other() string { return "from util.go" }

The build says undefined: other, though util.go is right there in the directory. The fix is to run the package, not the file. In the same directory, go run . compiles every file:

Shell
go run .
Text
from util.go

That command printed from util.go here. This is why your first program recommends go run . as the habit: it matches what go build does and does not depend on which files you remember to list.

Build constraints. A file whose name ends in _windows.go, _linux.go, _arm64.go and so on, or that has a //go:build line, is included only on matching platforms. A function defined only there is undefined on other platforms. Here the helper is declared in a Windows-only file:

main.goGo
package main

import "fmt"

func main() {
	fmt.Println(platform())
}
Text
# example.test/lab
./main.go:6:14: undefined: platform
platform_windows.goGo
package main

func platform() string { return "windows" }

On this macOS machine the compiler cannot see platform. The error reads undefined: platform once more, and nothing in it mentions the platform file, which is why this one is hard to spot. The fix is a file for each platform that defines the same function, or a shared default with a build constraint, as described in build tags and conditional compilation and OS and architecture file suffixes. Check with go list -f '{{.GoFiles}} {{.IgnoredGoFiles}}' ., which lists the files that were included and the ones that were ignored.

Module errors that look like undefined

An import of a package that is not in your module or its dependencies gives a different, longer message:

Text
main.go:3:8: no required module provides package example.test/lab/missing; to add it:
	go get example.test/lab/missing

I produced that by importing a package that does not exist in a fresh module. It is a module problem and not a scope problem. The compiler never got as far as looking for names. The fix is the command it prints, go get, for a third-party package, or go mod tidy, which adds every missing requirement and removes the unused ones. If the import is a package inside your own module, check the path: it is the module name from go.mod plus the directory, and a typo there gives the same message. The module basics are in how GOPATH gave way to Go modules.

A quick order of checks

When you meet undefined, ask in this order and stop at the first yes. Is the spelling and capitalization right, and does the message offer a but have? Is the package imported? Is the name declared in an enclosing scope, or only inside a block that ended? Is it unexported in another package? Are you running a single file with go run file.go? Does the defining file have a platform suffix or build tag that excludes it? Is it a generics or method-set problem, where the name exists on a type you are not holding? Most causes die at the first three questions, and the last two are the ones that need a command.

When the tool says something different from the editor

If the editor underlines a name and go build accepts it, or the reverse, the editor and the build are looking at different configurations. The usual reason is a build tag or a workspace setting, for example gopls analyzing for linux while you build for darwin. Set the same GOOS, GOARCH and -tags in the editor's environment. And if you recently moved files between packages, run go build ./... and not just the package you edited, so every importer gets rechecked. The specification on scope defines the rules the compiler applies, and the go build constraints documentation lists every way a file can be excluded.

Also inModules
Taggederror fix
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.