#1. Introduction to Go
Go (also known as Golang) is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. It was publicly announced in 2009 and has gained popularity for its simplicity, efficiency, and built-in concurrency support.
Key Features:
- Statically typed with type inference.
- Compiled to native machine code (fast execution).
- Garbage collected (automatic memory management).
- Built-in concurrency primitives: goroutines and channels.
- Rich standard library (net/http, JSON, crypto, etc.).
- Fast compilation and easy cross-compilation.
- Simple syntax (no classes, no inheritance, no exceptions).
Go is widely used for:
- Backend web services (APIs, microservices).
- Cloud-native tools (Docker, Kubernetes, Terraform are written in Go).
- Command-line tools.
- Networking and distributed systems.
#2. Installation and Setup
#Download and Install
Visit golang.org/dl and download the installer for your operating system.
On macOS (using Homebrew):
brew install go
On Windows: Download the MSI installer and run it.
On Linux: Download the tarball and extract to /usr/local:
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.profile
source ~/.profile
#Verify Installation
go version
#Workspace and Environment Variables (optional, Go Modules replace old GOPATH)
Modern Go uses modules for dependency management. You can work anywhere.
Set up environment variables (optional):
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
#Your First Go Program
Create a file hello.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Run it:
go run hello.go
Compile to binary:
go build hello.go
./hello # or hello.exe on Windows
#3. Basic Syntax
#Package Declaration
Every Go file belongs to a package. package main defines an executable program.
#Imports
import "fmt"
import "math/rand"
Or grouped:
import (
"fmt"
"math/rand"
)
#Functions
func add(x int, y int) int {
return x + y
}
Parameters of same type can be shortened:
func add(x, y int) int {
return x + y
}
#Multiple Returns
func swap(x, y string) (string, string) {
return y, x
}
#Named Return Values
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // naked return
}
#Variables
var name string = "John"
var age = 30 // type inferred
count := 10 // short declaration (inside functions only)
#Constants
const Pi = 3.14
#Comments
// Single line comment
/*
Multi-line comment
*/
#4. Variables and Data Types
#Basic Types
boolstring- Numeric types:
int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr - Floating:
float32,float64 - Complex:
complex64,complex128 - Byte:
byte(alias for uint8) - Rune:
rune(alias for int32, represents a Unicode code point)
#Zero Values
Variables declared without an initial value get a zero value:
0for numeric typesfalsefor boolean""for stringnilfor pointers, slices, maps, channels, functions, interfaces
#Type Conversion
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
Or simpler:
i := 42
f := float64(i)
#Constants
Constants can be character, string, boolean, or numeric values. They are created with const.
const (
StatusOK = 200
StatusNotFound = 404
)
#5. Control Structures
#If
if x > 0 {
fmt.Println("positive")
} else if x < 0 {
fmt.Println("negative")
} else {
fmt.Println("zero")
}
If with a short statement (variable scoped to if/else blocks):
if v := math.Pow(x, n); v < lim {
return v
}
#For
Go has only for (no while or do-while).
Basic for loop:
for i := 0; i < 10; i++ {
fmt.Println(i)
}
While-like:
sum := 1
for sum < 1000 {
sum += sum
}
Infinite loop:
for {
// break to exit
}
Range loop (for slices, maps, strings):
nums := []int{2, 4, 6}
for index, value := range nums {
fmt.Println(index, value)
}
#Switch
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS")
case "linux":
fmt.Println("Linux")
default:
fmt.Printf("%s\n", os)
}
No need for break; Go automatically breaks after each case. Use fallthrough to continue to next case.
Switch without condition (like if-else chain):
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
#Defer
defer postpones execution of a function until the surrounding function returns. Arguments are evaluated immediately but the call is deferred.
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
// Output: hello world
Deferred calls are pushed onto a stack. When a function returns, deferred calls are executed in last-in-first-out order.
#6. Functions
Functions are first-class citizens in Go.
#Function Syntax
func functionName(param1 type, param2 type) returnType {
// body
}
#Multiple Return Values
func getStats(nums []int) (sum int, avg float64) {
for _, v := range nums {
sum += v
}
avg = float64(sum) / float64(len(nums))
return
}
#Variadic Functions
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// Called as: sum(1,2,3) or sum([]int{1,2,3}...)
#Closures (Anonymous Functions)
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
#Function as Values
var fn func(int) int
fn = func(x int) int { return x * x }
result := fn(5)
#7. Packages and Modules
#Packages
Every Go file belongs to a package. The main package defines an executable. Other packages are reusable.
Create a package mypackage in folder mypackage/mypackage.go:
package mypackage
func Hello(name string) string {
return "Hello, " + name
}
Use it in main:
package main
import (
"fmt"
"yourmodule/mypackage"
)
func main() {
fmt.Println(mypackage.Hello("Alice"))
}
Note: Exported names start with capital letter.
#Modules
A module is a collection of Go packages with a go.mod file defining the module path and dependencies.
Initialize a new module:
go mod init github.com/username/project
Add dependencies:
go get github.com/gorilla/mux
Tidy up:
go mod tidy
#8. Arrays, Slices, and Maps
#Arrays
Fixed-size sequence of elements of the same type.
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // size inferred
#Slices
Dynamic-size, flexible view into an array.
slice := []int{1, 2, 3}
slice = append(slice, 4)
Create slice with make:
s := make([]int, 5, 10) // length 5, capacity 10
Slicing:
arr := [5]int{1,2,3,4,5}
s := arr[1:4] // [2,3,4]
Length and capacity:
len(s) // length
cap(s) // capacity
#Maps
Key-value pairs.
m := make(map[string]int)
m["key"] = 42
value := m["key"]
delete(m, "key")
Map literal:
m := map[string]int{
"apple": 5,
"pear": 3,
}
Check existence:
val, ok := m["apple"]
if ok {
fmt.Println(val)
}
#9. Structs and Methods
#Structs
A collection of fields.
type Person struct {
Name string
Age int
}
Create instances:
p1 := Person{"Alice", 30}
p2 := Person{Name: "Bob", Age: 25}
var p3 Person
p3.Name = "Charlie"
#Methods
Go does not have classes, but you can define methods on types.
func (p Person) SayHello() string {
return "Hello, I'm " + p.Name
}
Call: p1.SayHello()
Pointer receivers can modify the struct:
func (p *Person) Birthday() {
p.Age++
}
#Struct Embedding (Composition)
type Employee struct {
Person
EmployeeID int
}
Now Employee has all fields and methods of Person.
#10. Interfaces
Interfaces define a set of method signatures. Types implement interfaces implicitly.
type Greeter interface {
Greet() string
}
func (p Person) Greet() string {
return "Hello, " + p.Name
}
func printGreeting(g Greeter) {
fmt.Println(g.Greet())
}
Any type that implements Greet() satisfies Greeter.
#Empty Interface
interface{} (or any in Go 1.18+) can hold any value.
var i interface{} = 42
i = "hello"
#Type Assertion
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
// Safe assertion
s, ok := i.(string)
if ok {
fmt.Println(s)
}
#Type Switch
switch v := i.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Println("unknown")
}
#11. Error Handling
Go has no exceptions. Errors are returned as values.
#The error Interface
type error interface {
Error() string
}
Functions often return (result, error).
import "errors"
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
Check error:
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println(result)
}
#Custom Errors
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("error %d: %s", e.Code, e.Msg)
}
#Panic and Recover
panic stops normal execution, recover can regain control.
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}()
panic("something went wrong")
#12. Concurrency: Goroutines and Channels
#Goroutines
A goroutine is a lightweight thread managed by the Go runtime.
go func() {
fmt.Println("running in goroutine")
}()
#Channels
Channels are typed conduits for communication between goroutines.
Create:
ch := make(chan int)
Send/receive:
ch <- 42 // send
value := <-ch // receive
Buffered channels:
ch := make(chan int, 2) // buffer size 2
#Example: Worker Pool
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
results <- j * 2
}
}
func main() {
const numJobs = 10
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// start 3 workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// send jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// collect results
for r := 1; r <= numJobs; r++ {
<-results
}
}
#Select
select lets a goroutine wait on multiple channel operations.
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
case <-timeout:
fmt.Println("timeout")
default:
fmt.Println("no activity")
}
#Sync Package
Use sync.WaitGroup to wait for goroutines.
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()
sync.Mutex for mutual exclusion.
#13. Defer, Panic, and Recover
Already covered in section 5 and section 11.
#14. File I/O
#Reading Files
import "os"
import "io/ioutil" // deprecated, use os.ReadFile
data, err := os.ReadFile("file.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
Reading line by line:
file, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
#Writing Files
data := []byte("Hello, World!")
err := os.WriteFile("output.txt", data, 0644)
Append:
f, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
if _, err := f.WriteString("new line\n"); err != nil {
log.Fatal(err)
}
#Working with Directories
os.Mkdir("mydir", 0755)
os.MkdirAll("path/to/dir", 0755)
files, err := os.ReadDir(".")
#15. Testing
Go has a built-in testing framework.
Create a file math.go:
package math
func Add(a, b int) int {
return a + b
}
Create math_test.go:
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2,3) = %d; want %d", result, expected)
}
}
Run tests:
go test
go test -v
#Table-Driven Tests
func TestAdd(t *testing.T) {
tests := []struct {
a, b, expected int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tt := range tests {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d,%d) = %d; want %d", tt.a, tt.b, result, tt.expected)
}
}
}
#Benchmark
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
Run: go test -bench=.
#Coverage
go test -cover
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
#16. Building and Deploying
#Build for different platforms
GOOS=linux GOARCH=amd64 go build -o myapp-linux
GOOS=windows GOARCH=amd64 go build -o myapp.exe
GOOS=darwin GOARCH=amd64 go build -o myapp-mac
#Cross-compilation
Set GOOS and GOARCH.
#Dockerize
Create a Dockerfile:
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o myapp .
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
#Deploy to cloud
Build static binary and deploy to server or use container orchestration.
#17. Advanced Topics
#Reflection (reflect package)
Inspect types at runtime.
import "reflect"
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind:", v.Kind())
#Unsafe Package
Allows low-level programming, bypassing Go’s type safety. Use sparingly.
import "unsafe"
var x int = 42
ptr := unsafe.Pointer(&x)
#CGO (Calling C code)
Create a file with import "C" and use #cgo directives.
// #include <stdio.h>
import "C"
func main() {
C.puts(C.CString("Hello from C!"))
}
Requires cgo enabled (default if cross-compiling not needed).
#Code Generation (go generate)
Use go generate to run commands in files with //go:generate directive.
//go:generate stringer -type=Pill
type Pill int
const (
Aspirin Pill = iota
Ibuprofen
)
#Embedding Files (Go 1.16+)
import _ "embed"
//go:embed config.json
var configData []byte
#Generics (Go 1.18+)
func Map[T any, U any](s []T, f func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = f(v)
}
return result
}
#18. Best Practices
- Format your code with
go fmt. - Use
go modfor dependency management. - Write tests and aim for good coverage.
- Avoid global state; pass dependencies explicitly.
- Handle errors gracefully; don’t ignore them.
- Use interfaces for abstraction and testability.
- Keep concurrency simple; use channels for communication.
- Profile and benchmark critical code.
- Follow the Go Proverbs: https://go-proverbs.github.io/.
- Read effective Go: https://golang.org/doc/effective_go.
#Next Steps
Now that you’ve covered the fundamentals, it’s time to put your knowledge into practice.
- Learn the Fundamentals: For a deep dive into Go concepts, check out the comprehensive guide at Go Fundamentals. This resource covers everything from basic syntax to advanced topics, serving as an excellent theoretical companion.
- Hands-on Projects: To apply what you’ve learned, explore the go-projects repository. It features 10 self-contained projects that demonstrate key language features and standard library packages. You can build real applications, run tests, and see Go in action.
This guide covers the essential aspects of Go from beginner to advanced. Practice by building small projects, and refer to the official documentation for deeper dives. Happy coding!