Back to Blog

Go Keywords: All 25 Reserved Words Explained

Go has 25 keywords, unchanged since Go 1.0. See what each Go keyword does with a real example, plus predeclared names like any and len that are not keywords.

Go Keywords: All 25 Reserved Words Explained

Go (Golang) has 25 keywords: break, case, chan, const, continue, default, defer, else, fallthrough, for, func, go, goto, if, import, interface, map, package, range, return, select, struct, switch, type and var. They are reserved, so you cannot use them as variable, function or type names. The list has not changed since Go 1.0 in 2012, and Go 1.27 still has the same 25 (Go spec). Python has 35 keywords, Java has 51 and C++ has more than 80. The short list is on purpose. Go was designed to be small enough that you can hold the whole language in your head.

TL;DR

  • Go has exactly 25 keywords. The list is in the Go specification and has been the same since Go 1.0.
  • They fall into four groups. Declarations (package, import, const, var, type, func), composite types (struct, interface, map, chan), control flow (12 keywords from if to return) and concurrency and cleanup (go, select, defer).
  • There is only one loop keyword. for covers counted loops, while loops, infinite loops and, with range, iteration over slices, maps, channels, integers and iterator functions.
  • any, int, string, error, nil, true, len and make are not keywords. They are predeclared identifiers, so you can shadow them, even though you usually shouldn't.
  • Generics added no keywords. Go 1.18 added type parameters with predeclared names (any, comparable) instead of new reserved words.

The full list of Go keywords

KeywordCategoryWhat it does
packageDeclarationNames the package a file belongs to
importDeclarationBrings in other packages
constDeclarationDeclares a compile-time constant
varDeclarationDeclares a variable
typeDeclarationDeclares a named type or alias
funcDeclarationDeclares a function, method or function literal
structComposite typeGroups named fields into one type
interfaceComposite typeDeclares a method set (or a type constraint)
mapComposite typeDeclares a hash map type
chanComposite typeDeclares a channel type
ifControl flowRuns a block when a condition is true
elseControl flowRuns a block when the if condition is false
forControl flowThe only loop in Go
rangeControl flowIterates over slices, maps, strings, channels, integers and iterators
switchControl flowPicks one branch out of several
caseControl flowOne branch of a switch or select
defaultControl flowThe branch that runs when no case matches
fallthroughControl flowContinues into the next case of a switch
breakControl flowExits a for, switch or select
continueControl flowSkips to the next loop iteration
gotoControl flowJumps to a label in the same function
returnControl flowExits a function, optionally with values
goConcurrencyStarts a function in a new goroutine
selectConcurrencyWaits on several channel operations
deferCleanupRuns a call when the function returns

What are the declaration keywords in Go?

Go has six declaration keywords, and this small config loader uses all of them:

example.gogo
package main

import (
	"fmt"
	"time"
)

const defaultTimeout = 5 * time.Second

var maxRetries = 3

type Config struct {
	Addr    string
	Timeout time.Duration
}

func newConfig(addr string) Config {
	return Config{Addr: addr, Timeout: defaultTimeout}
}

func main() {
	cfg := newConfig(":8080")
	fmt.Println(cfg.Addr, cfg.Timeout, maxRetries)
}

package

Every Go file starts with a package clause. All files in one directory share the same package name. The package named main is special: it builds into an executable, and its main function is where the program starts. Any other name builds a library that other packages import. The Go package keyword covers naming rules, visibility and _test packages in depth.

import

import makes another package's exported names available in the file, like fmt.Println or http.ListenAndServe. You can group imports in parentheses, rename one (yaml "gopkg.in/yaml.v3") or import a package only for its side effects with _. Go refuses to compile a file with an unused import, so a file never lists a package it doesn't use. The Go import keyword covers aliases, blank and dot imports, and import cycles in depth.

const

const declares a value that is fixed at compile time, like a timeout or a header name. Constants can be untyped. const maxBodyBytes = 1 << 20 has no type until you use it, so it works as an int or an int64 without a conversion. Inside a const block, the predeclared identifier iota counts up from 0, which is how Go writes enums. The Go const keyword covers typed and untyped constants, iota and enums in depth.

var

var declares a variable with an explicit type, an initial value or both. Every variable starts at its type's zero value: 0, "", false or nil. Inside functions most Go code uses the short form name := value instead. var is still the right choice at package level and when you want the zero value on purpose, as in var buf bytes.Buffer. The Go var keyword covers zero values, :=, shadowing and package-level variables in depth.

type

type declares a new named type, such as a struct, an interface or a named version of an existing type like type UserID int64. The new type can have its own methods. With = it declares an alias instead (type Handler = http.Handler), which is just a second name for the same type. The Go type keyword covers defined types, aliases, methods and generic types in depth.

func

func declares functions and methods, and it also creates function literals (closures). A method has a receiver before its name: func (s *Server) Start() error. Go functions can return several values, which is how errors are reported: func parse(body []byte) (Request, error). The Go func keyword covers methods, closures, variadic functions and functional options in depth.

Which Go keywords build composite types?

Four keywords build types out of other types. Arrays, slices and pointers are composite types too, but they use symbols ([N]T, []T, *T) instead of keywords.

example.gogo
type Notifier interface {
	Notify(ctx context.Context, msg string) error
}

type SlackNotifier struct {
	Channel string
}

func (s SlackNotifier) Notify(ctx context.Context, msg string) error {
	fmt.Printf("#%s: %s\n", s.Channel, msg)
	return nil
}

func main() {
	var n Notifier = SlackNotifier{Channel: "alerts"}

	statusCounts := map[int]int{200: 41, 500: 2}
	events := make(chan string, 10)

	events <- fmt.Sprintf("%d server errors", statusCounts[500])
	n.Notify(context.Background(), <-events)
}

struct

struct groups named fields into one type. It is Go's replacement for classes: you attach methods to a struct type, and you reuse behavior by embedding one struct in another instead of inheriting from it. Struct tags like json:"email" tell packages such as encoding/json how to encode each field. The Go struct keyword covers struct tags, value vs pointer receivers, embedding and comparing and copying structs in depth.

interface

interface declares a set of methods. A type satisfies an interface just by having those methods, with no implements declaration, so SlackNotifier above is a Notifier without saying so. Since Go 1.18, interfaces also describe type constraints for generics, such as interface{ ~int | ~float64 }. The Go interface keyword covers implicit satisfaction, nil interfaces, type assertions and constraints in depth.

map

map declares a hash map type, written map[KeyType]ValueType. You create one with a literal or with make, and a nil map panics on write. Reading a missing key returns the zero value, and the two-value form count, ok := statusCounts[404] tells you whether the key was there. The spec leaves iteration order unspecified, and the runtime randomizes it on purpose. var vs make in Go explains why a nil map panics on write when a nil slice doesn't. The Go map keyword covers the comma-ok idiom, iteration order, sets, concurrent access and the maps package in depth.

chan

chan declares a channel type, the pipe that goroutines use to pass values to each other. chan<- Job is send-only and <-chan Job is receive-only, which lets a function signature say which direction it uses. Channels are created with make, and a buffered channel (make(chan string, 10)) holds values until someone receives them. The Go chan keyword covers buffered and unbuffered channels, closing, deadlocks and select patterns in depth.

Which Go keywords control the flow of a program?

Twelve keywords handle branching and loops. Go leaves out while, do, try, catch and the ternary operator. Errors are ordinary return values that you check with if.

example.gogo
func parse(body []byte) (Request, error) {
	var req Request
	if err := json.Unmarshal(body, &req); err != nil {
		return Request{}, fmt.Errorf("decode request: %w", err)
	}
	if req.Email == "" {
		return Request{}, errors.New("email is required")
	}
	return req, nil
}

func countErrors(lines []string) int {
	errs := 0
	for _, line := range lines {
		if strings.HasPrefix(line, "#") {
			continue
		}
		if line == "EOF" {
			break
		}
		if strings.Contains(line, "level=error") {
			errs++
		}
	}
	return errs
}

if

if runs a block when a condition is true. The condition needs no parentheses, but the braces are required. An if can start with a short statement, and the variable it declares only exists inside the if and its else branches. Go code uses this form constantly, as in if err := json.Unmarshal(body, &req); err != nil. The Go if keyword covers bool-only conditions, init statements, shadowing, early returns and the missing ternary in depth.

else

else runs when the if condition is false, and else if chains more conditions. Idiomatic Go uses else less than most languages. When the if branch ends in return, the rest of the function is already the else case, so Go code keeps the happy path unindented and returns early on errors. The Go else keyword covers else if chains, if-statement scope, early returns and the missing ternary operator in depth.

for

for is Go's only loop keyword. for i := 0; i < n; i++ is the classic counted loop, for queue.Len() > 0 is a while loop and for { ... } loops forever until something breaks out. Since Go 1.22, in modules whose go.mod declares go 1.22 or later, each iteration gets its own copy of the loop variable, so closures and goroutines started in a loop no longer share one variable. The Go for keyword covers the three loop forms, range, Go 1.22 loop variables, labels and common loop mistakes in depth.

range

range makes a for loop iterate over a collection. It gives you index and value for slices and arrays, key and value for maps, byte offset and rune for strings, and received values for channels. Go 1.22 added ranging over an integer (for attempt := range 3 runs with 0, 1 and 2), and Go 1.23 added ranging over iterator functions such as the one maps.Keys(m) returns.

switch

switch picks one branch out of several. Unlike C and Java, a Go switch stops after the matching case, so you never need a break at the end of each case. A switch with no expression works like a cleaner if/else if chain, and a type switch (switch v := x.(type)) branches on the dynamic type of an interface value.

case

case marks one branch of a switch or a select. A single case can list several values: case "GET", "HEAD":. In a switch the cases are checked from top to bottom, and the first match wins.

default

default is the branch that runs when no case matches. It can appear anywhere in the switch, though convention puts it last. In a select, a default case makes the whole statement non-blocking: if no channel is ready, default runs right away.

fallthrough

fallthrough makes a switch continue into the next case's body without checking that case's condition. It is rare in real code, and it is the one place where a Go switch behaves like C. Tiered permissions are a case where it reads well:

example.gogo
func permissions(role string) []string {
	var perms []string
	switch role {
	case "admin":
		perms = append(perms, "delete")
		fallthrough
	case "editor":
		perms = append(perms, "write")
		fallthrough
	case "viewer":
		perms = append(perms, "read")
	default:
		return nil
	}
	return perms
}

permissions("editor") returns [write read] and permissions("admin") returns [delete write read]. fallthrough must be the last statement in a case, and it is not allowed in a type switch.

break

break exits the innermost for, switch or select. That last part is a common trap: a break inside a select only leaves the select, not the loop around it. To leave the loop, put a label on it and write break loop:

example.gogo
func drain(ctx context.Context, events <-chan string) {
loop:
	for {
		select {
		case ev, ok := <-events:
			if !ok {
				break loop
			}
			fmt.Println("event:", ev)
		case <-ctx.Done():
			break loop
		}
	}
	fmt.Println("drained")
}

continue

continue skips the rest of the current iteration and starts the next one. In countErrors above, it skips comment lines. Like break, it can take a label to continue an outer loop from inside a nested one.

goto

goto jumps to a label in the same function. It cannot jump into a block or over a variable declaration, which rules out most of the problems goto has in C. You will rarely write it. It shows up mainly in generated code and in a few low-level parts of the standard library. LevelUpGo's own backend has about 30,000 lines of Go outside tests and seed data. It uses goto and fallthrough zero times, compared with more than 250 range loops and more than 40 defer calls.

return

return ends a function and hands back its results. Go functions often return two values, a result and an error, and the caller checks the error right away. With named result parameters, a bare return returns their current values, but most style guides keep that for short functions only.

Which Go keywords handle concurrency and cleanup?

The last three keywords are go, select and defer. Together with chan, they are what a Go server uses to run work in parallel and clean up afterwards.

example.gogo
func startWorkers(ctx context.Context, n int, jobs <-chan string, results chan<- []byte) {
	for range n {
		go worker(ctx, jobs, results)
	}
}

func worker(ctx context.Context, jobs <-chan string, results chan<- []byte) {
	for {
		select {
		case url, ok := <-jobs:
			if !ok {
				return
			}
			body, err := fetch(ctx, url)
			if err != nil {
				log.Printf("fetch %s: %v", url, err)
				continue
			}
			results <- body
		case <-ctx.Done():
			return
		}
	}
}

func fetch(ctx context.Context, url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return io.ReadAll(resp.Body)
}

go

go starts a function call in a new goroutine and moves on without waiting for it. startWorkers above runs go worker(ctx, jobs, results) in a loop, which gives you a worker pool. Goroutines start with a few kilobytes of stack, so a server can run hundreds of thousands of them. The go statement discards the function's return values, so results come back through channels, and a sync.WaitGroup or errgroup waits for the work to finish.

select

select waits on several channel operations and runs the first one that is ready. If several are ready at once, it picks one at random. The worker above uses it to take the next job or stop when the context is cancelled. Pairing a channel with ctx.Done() or time.After is how Go code adds timeouts and cancellation to anything that blocks.

defer

defer schedules a call to run when the surrounding function returns, whether it returns normally or panics. It keeps cleanup next to the code that needs it: defer resp.Body.Close(), defer mu.Unlock(), defer cancel(). Deferred calls run in last-in, first-out order, and their arguments are evaluated when the defer line runs, not at the end. A defer inside a loop only runs when the whole function returns, which is one of the common Go mistakes worth knowing early.

What are not keywords in Go?

Many names that look built in are predeclared identifiers, not keywords. The Go spec lists them in the universe block, which surrounds every package (Go spec):

KindPredeclared identifiers
Typesany, bool, byte, comparable, complex64, complex128, error, float32, float64, int, int8, int16, int32, int64, rune, string, uint, uint8, uint16, uint32, uint64, uintptr
Constantstrue, false, iota
Zero valuenil
Functionsappend, cap, clear, close, complex, copy, delete, imag, len, make, max, min, new, panic, print, println, real, recover

The difference matters because predeclared identifiers can be redeclared in an inner scope. This compiles:

example.gogo
func logAddrChange(cur, next Config) {
	old, new := cur.Addr, next.Addr
	if old != new {
		log.Printf("addr changed from %s to %s", old, new)
	}
}

func countLines(lines []string) int {
	len := len(lines)
	return len
}

Naming a variable new is common in code that compares old and new values, and it works. After len := len(lines), though, any later len(...) call in that function fails with invalid operation: cannot call len (variable of type int): int is not a function. A keyword can't be used like this at all. var := 3 fails with syntax error: unexpected :=, expected name. Linters such as predeclared in golangci-lint flag shadowed built-ins, because code like countLines compiles but confuses the next reader.

Keeping these names out of the keyword list lets Go grow without breaking existing programs. any (Go 1.18) and min, max and clear (Go 1.21) were added as predeclared identifiers, so any code that already used those names as variables kept compiling.

Where LevelUpGo fits

LevelUpGo teaches Go through exercises that run real Go code in the browser. The Go Basics course covers the declaration and control flow keywords, from var and const to switch and for. Concurrency Fundamentals covers go, chan, select and defer, and Simplification shows when a switch reads better than an if chain. The Training Ground has short standalone exercises for practicing them outside a course.

FAQ

How many keywords does Go have?

Go has 25 keywords: break, case, chan, const, continue, default, defer, else, fallthrough, for, func, go, goto, if, import, interface, map, package, range, return, select, struct, switch, type and var. The list is defined in the Go specification and has not changed since Go 1.0.

Is any a keyword in Go?

No. any is a predeclared identifier, an alias for interface{} added in Go 1.18. The same goes for int, string, error, nil, true, false, iota and built-in functions like len, make and append. You can redeclare them in an inner scope, although linters warn when you do.

Can I use a Go keyword as a variable name?

No. Keywords are reserved, so var := 3 or func type() is a syntax error. The usual workaround is a short variation: Go code often uses typ or kind instead of type, pkg instead of package and fn instead of func.

Has Go added keywords since Go 1.0?

No. The 25 keywords in Go 1.27 are the same 25 that shipped in Go 1.0 in March 2012. Generics, range over integers, iterator functions and the generic methods in Go 1.27 all arrived without new keywords. The Go 1 compatibility promise makes new keywords unlikely, because a new reserved word would break every program already using it as a name.

Why does Go have so few keywords?

The designers wanted it that way. The Go FAQ says they "tried to reduce clutter and complexity" and that Go's "syntax is clean and light on keywords." That is why Go has one loop keyword instead of three, and why errors are return values instead of try/catch. The result is a language you can learn quickly, and Go code from different codebases reads much the same.

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