Complete Guide to Golang Keywords - Understanding Every Go Keyword
A comprehensive guide to all 25 keywords in the Go programming language. Learn what each keyword does, when to use it, and see practical examples for every Go keyword.
Complete Guide to Golang Keywords - Understanding Every Go Keyword
Go (Golang) is a statically typed, compiled programming language designed for simplicity and efficiency. One of Go’s strengths is its minimal keyword set—only 25 keywords compared to languages like C++ (84) or Java (50+). This simplicity makes Go easier to learn while remaining powerful enough to build complex systems.
This guide covers all 25 Go keywords, explaining what each does, when to use it, and providing practical examples.
Go Keywords Overview
Go’s 25 keywords can be categorized into:
- Declaration keywords:
var,const,type,func,package,import - Control flow:
if,else,switch,case,default,for,break,continue,goto,fallthrough,return - Concurrency:
go,chan,select,defer - Type system:
interface,struct,map - Error handling: (implicit through
ifandreturn)
Let’s explore each keyword in detail.
Declaration Keywords
package
What it does: Declares the package name for the current file. Every Go file must start with a package declaration.
When to use: Always at the top of every .go file.
Example:
package main // Executable program
package utils // Library package
Notes:
package maincreates an executable program- Other package names create reusable libraries
- Package name should match the directory name (convention)
import
What it does: Imports packages for use in the current file.
When to use: After the package declaration to bring in external or standard library packages.
Example:
import (
"fmt"
"os"
"time"
"github.com/gin-gonic/gin" // Third-party package
)
// Single import
import "fmt"
// Import with alias
import f "fmt"
import _ "database/sql" // Blank import (side effects only)
Notes:
- Multiple imports can be grouped in parentheses
- Blank import (
_) is used for side effects (like package initialization) - Aliased imports help avoid naming conflicts
var
What it does: Declares one or more variables with explicit type or type inference.
When to use: When you need to declare variables that may be initialized later or when you want explicit typing.
Example:
// Single variable
var name string
var age int = 25
// Multiple variables
var x, y int = 1, 2
var (
username string = "john"
isActive bool = true
)
// Type inference
var count = 10 // int inferred
// Zero values
var name string // ""
var age int // 0
var active bool // false
Notes:
- Variables declared with
varhave zero values if not initialized - Can be used at package or function level
- Type can be omitted if value is provided (type inference)
const
What it does: Declares constants—values that cannot be changed after declaration.
When to use: For values that should never change (configuration, magic numbers, enums).
Example:
// Single constant
const Pi = 3.14159
const MaxUsers = 1000
// Typed constant
const StatusOK int = 200
// Multiple constants
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
// Iota for enumerations
const (
Monday = iota // 0
Tuesday // 1
Wednesday // 2
)
// Expression constants
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
Notes:
- Constants must be compile-time evaluable
iotais useful for creating enumerated constants- Constants can be untyped (more flexible) or typed (more strict)
type
What it does: Declares a new type (type alias or type definition) or defines custom types.
When to use: To create type aliases, custom types, structs, interfaces, or function types.
Example:
// Type alias
type ID = int64
// Custom type
type UserID int64
type Status string
// Struct type
type User struct {
ID int64
Name string
Email string
IsActive bool
}
// Interface type
type Writer interface {
Write([]byte) (int, error)
}
// Function type
type Handler func(string) error
// Slice type
type Users []User
// Map type
type UserMap map[string]User
Notes:
- Type alias (
=) creates a new name for existing type - Type definition creates a distinct new type (even if underlying type is same)
- Used extensively for creating domain models and abstractions
func
What it does: Declares a function or method.
When to use: To define reusable blocks of code, methods on types, or function values.
Example:
// Simple function
func greet(name string) {
fmt.Printf("Hello, %s\n", name)
}
// Function with return value
func add(a, b int) int {
return a + b
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// Named return values
func calculate(x, y int) (sum int, product int) {
sum = x + y
product = x * y
return // Naked return
}
// Variadic function
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
// Method (function with receiver)
func (u User) GetFullName() string {
return u.Name
}
// Pointer receiver
func (u *User) UpdateEmail(email string) {
u.Email = email
}
// Function as value
var handler func(string) = greet
Notes:
- Functions are first-class citizens in Go
- Methods are functions with receivers
- Pointer receivers allow modifying the receiver
- Variadic functions accept variable number of arguments
Control Flow Keywords
if
What it does: Executes code conditionally based on a boolean expression.
When to use: For conditional execution, error checking, and branching logic.
Example:
// Basic if
if age >= 18 {
fmt.Println("Adult")
}
// If-else
if score >= 90 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else {
fmt.Println("C")
}
// If with initialization (common pattern)
if err := doSomething(); err != nil {
return err
}
// If with multiple conditions
if x > 0 && y > 0 {
fmt.Println("Both positive")
}
// If with assignment and condition
if result, err := process(); err == nil {
fmt.Println(result)
}
Notes:
- Go’s
ifcan include an initialization statement - This pattern is idiomatic for error handling
- No parentheses around condition (unlike C/Java)
else
What it does: Provides alternative execution path when if condition is false.
When to use: With if statements when you need an alternative branch.
Example:
if user.IsActive {
fmt.Println("User is active")
} else {
fmt.Println("User is inactive")
}
// else if chain
if score >= 90 {
grade = "A"
} else if score >= 80 {
grade = "B"
} else if score >= 70 {
grade = "C"
} else {
grade = "F"
}
Notes:
- Must be on the same line as closing brace of
if - Can chain multiple
else ifstatements - Final
elseis optional
switch
What it does: Multi-way branching based on value comparison or boolean expressions.
When to use: When you have multiple conditions to check (cleaner than long if-else chains).
Example:
// Expression switch
switch day {
case "Monday":
fmt.Println("Start of week")
case "Friday":
fmt.Println("End of week")
case "Saturday", "Sunday":
fmt.Println("Weekend")
default:
fmt.Println("Midweek")
}
// Switch with initialization
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS")
case "linux":
fmt.Println("Linux")
default:
fmt.Println("Other")
}
// Type switch
switch v := x.(type) {
case int:
fmt.Printf("Integer: %d\n", v)
case string:
fmt.Printf("String: %s\n", v)
default:
fmt.Printf("Unknown type\n")
}
// Switch without expression (like if-else)
switch {
case x < 0:
fmt.Println("Negative")
case x == 0:
fmt.Println("Zero")
default:
fmt.Println("Positive")
}
Notes:
- No
breakneeded (unlike C/Java)—Go automatically breaks - Can switch on values, types, or use no expression
defaultcase is optional- Multiple values in one
caseare allowed
case
What it does: Defines a branch in a switch statement.
When to use: Within switch statements to specify matching conditions.
Example:
switch status {
case 200:
fmt.Println("OK")
case 404:
fmt.Println("Not Found")
case 500:
fmt.Println("Server Error")
}
// Multiple values
case "red", "blue", "green":
fmt.Println("Primary color")
// Fallthrough to next case
case 1:
fmt.Println("One")
fallthrough
case 2:
fmt.Println("One or Two")
Notes:
- Each
caseis automatically terminated (no fallthrough) - Use
fallthroughto continue to next case - Cases are evaluated top-to-bottom
default
What it does: Defines the default branch in a switch statement when no cases match.
When to use: In switch statements to handle unmatched cases.
Example:
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("Weekday")
case "Saturday", "Sunday":
fmt.Println("Weekend")
default:
fmt.Println("Unknown day")
}
Notes:
- Optional in
switchstatements - Executes when no
casematches - Typically placed last (though order doesn’t matter)
for
What it does: Creates loops for iteration. Go’s only looping construct (no while or do-while).
When to use: For all types of loops—counting, iterating over collections, infinite loops, or conditional loops.
Example:
// Traditional for loop (C-style)
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// While-style loop (condition only)
for x < 100 {
x *= 2
}
// Infinite loop
for {
// Do something
if shouldBreak {
break
}
}
// Range over slice
for index, value := range numbers {
fmt.Printf("%d: %d\n", index, value)
}
// Range over map
for key, value := range userMap {
fmt.Printf("%s: %v\n", key, value)
}
// Range over string (runes)
for i, char := range "Hello" {
fmt.Printf("%d: %c\n", i, char)
}
// Range over channel
for value := range ch {
fmt.Println(value)
}
// Ignore index/value
for _, value := range slice {
fmt.Println(value)
}
Notes:
- Go’s only loop keyword (replaces
while,do-while,foreach) rangekeyword works withforfor iteration- Can iterate over slices, maps, strings, channels
- Use
_to ignore index or value
break
What it does: Exits the innermost for, switch, or select statement.
When to use: To exit loops early or exit switch/select blocks.
Example:
// Break from for loop
for i := 0; i < 10; i++ {
if i == 5 {
break // Exits loop
}
fmt.Println(i)
}
// Break from nested loops (with label)
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i*j == 4 {
break outer // Breaks outer loop
}
}
}
// Break from switch (rarely needed, auto-break)
switch x {
case 1:
fmt.Println("One")
break // Usually unnecessary
}
Notes:
- Exits the innermost loop/switch/select
- Can use labels to break from outer loops
- In
switch,breakis usually unnecessary (auto-break)
continue
What it does: Skips the rest of the current loop iteration and continues with the next iteration.
When to use: To skip certain iterations in a loop without exiting the loop.
Example:
// Skip even numbers
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // Skip to next iteration
}
fmt.Println(i) // Only prints odd numbers
}
// Continue with label
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer // Continue outer loop
}
fmt.Printf("%d-%d\n", i, j)
}
}
Notes:
- Skips remaining code in current iteration
- Can use labels to continue outer loops
- Useful for filtering or skipping invalid data
goto
What it does: Transfers control to a labeled statement (unconditional jump).
When to use: Rarely. Generally avoided, but can be useful for error handling or breaking out of nested loops.
Example:
// Error handling pattern
if err := doSomething(); err != nil {
goto cleanup
}
if err := doSomethingElse(); err != nil {
goto cleanup
}
// ... more code
cleanup:
// Cleanup code
close(resources)
return err
// Breaking from nested loops
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if found {
goto done
}
}
}
done:
fmt.Println("Found!")
Notes:
- Generally discouraged in Go (and most languages)
- Can be useful for error handling or complex control flow
- Cannot jump into other scopes (only within function)
- Use labels (identifier followed by colon)
fallthrough
What it does: In a switch statement, continues execution to the next case instead of breaking.
When to use: When you want multiple cases to execute (rare).
Example:
switch value {
case 1:
fmt.Println("One")
fallthrough // Continue to next case
case 2:
fmt.Println("One or Two")
case 3:
fmt.Println("Three")
// No fallthrough, so breaks here
}
// If value is 1, prints "One" then "One or Two"
Notes:
- Only works in
switchstatements - Transfers control to next
case(even if condition doesn’t match) - Must be last statement in
case(compiler error otherwise) - Use sparingly—can make code harder to understand
return
What it does: Exits the current function and optionally returns values.
When to use: To exit a function early or return function results.
Example:
// No return value
func greet(name string) {
if name == "" {
return // Early exit
}
fmt.Printf("Hello, %s\n", name)
}
// Single return value
func add(a, b int) int {
return a + b
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// Named return values (naked return)
func calculate(x, y int) (sum int, product int) {
sum = x + y
product = x * y
return // Returns sum and product
}
Notes:
- Exits function immediately
- Must return all declared return values (unless named returns)
- Naked return uses named return values
- Common pattern: return value and error
Concurrency Keywords
go
What it does: Starts a new goroutine (lightweight thread) that runs concurrently.
When to use: To run functions concurrently, enabling parallel execution.
Example:
// Simple goroutine
go fmt.Println("Hello from goroutine")
// Goroutine with function
go processData(data)
// Anonymous function goroutine
go func() {
fmt.Println("Running in goroutine")
}()
// Goroutine with parameters
go func(msg string) {
fmt.Println(msg)
}("Hello")
// Multiple goroutines
for i := 0; i < 10; i++ {
go func(id int) {
fmt.Printf("Goroutine %d\n", id)
}(i)
}
// Wait for goroutines
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Worker %d\n", id)
}(i)
}
wg.Wait()
Notes:
- Goroutines are lightweight (thousands can run simultaneously)
- Main goroutine must not exit before others finish
- Use channels or
sync.WaitGroupfor coordination - No return values from goroutines (use channels)
chan
What it does: Declares a channel type for communication between goroutines.
When to use: To create channels for sending/receiving data between goroutines.
Example:
// Unbuffered channel
var ch chan int
ch = make(chan int)
// Buffered channel
ch := make(chan int, 10)
// Send-only channel
var sendCh chan<- int
// Receive-only channel
var recvCh <-chan int
// Channel operations
ch <- 42 // Send
value := <-ch // Receive
value, ok := <-ch // Receive with check
// Close channel
close(ch)
// Range over channel
for value := range ch {
fmt.Println(value)
}
Notes:
- Channels are typed (e.g.,
chan int) - Unbuffered channels block until sender/receiver ready
- Buffered channels have capacity
<-operator for send/receive- Close channels to signal no more values
select
What it does: Chooses which of multiple channel operations can proceed (like switch for channels).
When to use: To wait on multiple channels, implement timeouts, or non-blocking operations.
Example:
// Select with multiple channels
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
case ch3 <- 42:
fmt.Println("Sent to ch3")
}
// Select with default (non-blocking)
select {
case msg := <-ch:
fmt.Println("Received:", msg)
default:
fmt.Println("No message ready")
}
// Select with timeout
select {
case msg := <-ch:
fmt.Println("Received:", msg)
case <-time.After(5 * time.Second):
fmt.Println("Timeout")
}
// Select with context cancellation
select {
case <-ctx.Done():
return ctx.Err()
case result := <-ch:
return result
}
Notes:
- Executes first ready case (random if multiple ready)
defaultcase makes it non-blocking- Commonly used with timeouts and context cancellation
- Essential for channel-based concurrency patterns
defer
What it does: Schedules a function call to execute when the surrounding function returns.
When to use: For cleanup (closing files, unlocking mutexes), ensuring code runs even on panic.
Example:
// Basic defer
func readFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // Always closes, even if function returns early
// Use file...
return nil
}
// Multiple defers (LIFO order)
func example() {
defer fmt.Println("Third")
defer fmt.Println("Second")
defer fmt.Println("First")
// Prints: First, Second, Third (reverse order)
}
// Defer with function
defer func() {
fmt.Println("Cleanup")
}()
// Defer with arguments (evaluated immediately)
i := 0
defer fmt.Println(i) // Prints 0 (not current value)
i++
// Defer modifying return value
func example() (result int) {
defer func() {
result++ // Modifies named return
}()
return 0 // Returns 1, not 0
}
Notes:
- Defers execute in LIFO (Last In, First Out) order
- Arguments evaluated immediately, function called later
- Always executes, even on panic or early return
- Idiomatic for resource cleanup
Type System Keywords
interface
What it does: Defines an interface type—a set of method signatures that types must implement.
When to use: To define contracts for behavior, enable polymorphism, and create abstractions.
Example:
// Basic interface
type Writer interface {
Write([]byte) (int, error)
}
// Interface with multiple methods
type ReadWriter interface {
Reader
Writer
}
// Empty interface (accepts any type)
func printAnything(v interface{}) {
fmt.Println(v)
}
// Type assertion
var w Writer
w = os.Stdout
file, ok := w.(*os.File)
// Type switch
switch v := x.(type) {
case string:
fmt.Println("String:", v)
case int:
fmt.Println("Int:", v)
}
// Interface implementation (implicit)
type MyWriter struct{}
func (m MyWriter) Write(data []byte) (int, error) {
// Implementation
return len(data), nil
}
// MyWriter automatically implements Writer interface
Notes:
- Interfaces are implemented implicitly (no
implementskeyword) - Empty interface
interface{}accepts any type - Type assertions and type switches work with interfaces
- Interfaces enable polymorphism and dependency injection
struct
What it does: Defines a structured type that groups together fields of different types.
When to use: To create custom data types, models, and structured data.
Example:
// Basic struct
type Person struct {
Name string
Age int
}
// Struct with tags
type User struct {
ID int64 `json:"id" db:"user_id"`
Email string `json:"email" db:"email"`
Password string `json:"-" db:"password"` // Hidden in JSON
}
// Embedded struct (composition)
type Employee struct {
Person // Embedded
EmployeeID int
Salary float64
}
// Anonymous struct
person := struct {
Name string
Age int
}{
Name: "John",
Age: 30,
}
// Struct methods
func (p Person) GetName() string {
return p.Name
}
// Pointer receiver method
func (p *Person) SetAge(age int) {
p.Age = age
}
// Struct initialization
p1 := Person{"John", 30}
p2 := Person{Name: "Jane", Age: 25}
p3 := Person{Name: "Bob"} // Age is 0 (zero value)
Notes:
- Structs are value types (copied when passed)
- Use pointer receivers to modify structs
- Embedded structs enable composition
- Tags used for serialization, validation, etc.
map
What it does: Declares a map type—an unordered collection of key-value pairs.
When to use: For key-value lookups, dictionaries, hash tables, and associative arrays.
Example:
// Map declaration
var userMap map[string]int
userMap = make(map[string]int)
// Map initialization
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
// Map operations
ages["Charlie"] = 28 // Insert/update
age := ages["Alice"] // Read
delete(ages, "Bob") // Delete
// Check if key exists
age, exists := ages["Alice"]
if exists {
fmt.Println(age)
}
// Iterate over map
for name, age := range ages {
fmt.Printf("%s: %d\n", name, age)
}
// Map of slices
groups := map[string][]string{
"fruits": {"apple", "banana"},
"colors": {"red", "blue"},
}
// Nested maps
nested := map[string]map[string]int{
"a": {"x": 1, "y": 2},
"b": {"x": 3, "y": 4},
}
Notes:
- Maps are reference types (passed by reference)
- Zero value is
nil(cannot write to nil map) - Keys must be comparable types
- Order is not guaranteed (randomized in Go 1.0+)
- Use
make()or literal to initialize
Reserved Words (Not Keywords)
These are not keywords but are reserved and cannot be used as identifiers:
true,false: Boolean constantsnil: Zero value for pointers, interfaces, slices, maps, channels, and function typesiota: Used in constant declarations for generating incrementing values
iota Example
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
)
const (
_ = iota // 0 (ignored)
KB = 1 << (10 * iota) // 1024
MB // 1048576
GB // 1073741824
)
Keyword Usage Patterns
Common Idioms
Error Handling:
if err := doSomething(); err != nil {
return err
}
Resource Cleanup:
file, err := os.Open("file.txt")
if err != nil {
return err
}
defer file.Close()
Goroutine with Channel:
ch := make(chan int)
go func() {
ch <- compute()
}()
result := <-ch
Context with Timeout:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
select {
case <-ctx.Done():
return ctx.Err()
case result := <-ch:
return result
}
Summary
Go’s 25 keywords provide a minimal yet powerful foundation:
| Category | Keywords | Count |
|---|---|---|
| Declaration | package, import, var, const, type, func | 6 |
| Control Flow | if, else, switch, case, default, for, break, continue, goto, fallthrough, return | 11 |
| Concurrency | go, chan, select, defer | 4 |
| Type System | interface, struct, map | 3 |
| Reserved | true, false, nil, iota | 4 (not keywords) |
Key Takeaways
- Simplicity: Only 25 keywords make Go easy to learn
- Power: These keywords enable complex systems
- Concurrency: Built-in support with
go,chan,select - Composition:
interfaceandstructenable flexible design - Idioms: Learn common patterns for effective Go code
Understanding these keywords is fundamental to writing idiomatic Go code. Each keyword serves a specific purpose, and together they form a cohesive language that balances simplicity with power.
Further Reading
Master these keywords, and you’ll have a solid foundation for building robust, concurrent, and efficient systems in Go! 🚀