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 fromiftoreturn) and concurrency and cleanup (go,select,defer). - There is only one loop keyword.
forcovers counted loops, while loops, infinite loops and, withrange, iteration over slices, maps, channels, integers and iterator functions. any,int,string,error,nil,true,lenandmakeare 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
| Keyword | Category | What it does |
|---|---|---|
package | Declaration | Names the package a file belongs to |
import | Declaration | Brings in other packages |
const | Declaration | Declares a compile-time constant |
var | Declaration | Declares a variable |
type | Declaration | Declares a named type or alias |
func | Declaration | Declares a function, method or function literal |
struct | Composite type | Groups named fields into one type |
interface | Composite type | Declares a method set (or a type constraint) |
map | Composite type | Declares a hash map type |
chan | Composite type | Declares a channel type |
if | Control flow | Runs a block when a condition is true |
else | Control flow | Runs a block when the if condition is false |
for | Control flow | The only loop in Go |
range | Control flow | Iterates over slices, maps, strings, channels, integers and iterators |
switch | Control flow | Picks one branch out of several |
case | Control flow | One branch of a switch or select |
default | Control flow | The branch that runs when no case matches |
fallthrough | Control flow | Continues into the next case of a switch |
break | Control flow | Exits a for, switch or select |
continue | Control flow | Skips to the next loop iteration |
goto | Control flow | Jumps to a label in the same function |
return | Control flow | Exits a function, optionally with values |
go | Concurrency | Starts a function in a new goroutine |
select | Concurrency | Waits on several channel operations |
defer | Cleanup | Runs 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.gogopackage 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.gogotype 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.gogofunc 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.gogofunc 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.gogofunc 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.gogofunc 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):
| Kind | Predeclared identifiers |
|---|---|
| Types | any, bool, byte, comparable, complex64, complex128, error, float32, float64, int, int8, int16, int32, int64, rune, string, uint, uint8, uint16, uint32, uint64, uintptr |
| Constants | true, false, iota |
| Zero value | nil |
| Functions | append, 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.gogofunc 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
- The Go Programming Language Specification, Keywords: https://go.dev/ref/spec#Keywords
- The Go Programming Language Specification, Predeclared identifiers: https://go.dev/ref/spec#Predeclared_identifiers
- Go 1 and the Future of Go Programs (compatibility promise): https://go.dev/doc/go1compat
- Go FAQ, "What are the guiding principles in the design?": https://go.dev/doc/faq#principles
- Go 1.22 release notes (loop variables, range over integers): https://go.dev/doc/go1.22
- Go 1.23 release notes (range over iterator functions): https://go.dev/doc/go1.23
- Python language reference, Keywords: https://docs.python.org/3/reference/lexical_analysis.html#keywords
- Java Language Specification (Java SE 21), Keywords: https://docs.oracle.com/javase/specs/jls/se21/html/jls-3.html#jls-3.9
- cppreference, C++ keywords: https://en.cppreference.com/w/cpp/keyword
