Go Language Basics - Complete Guide
Comprehensive guide to Go fundamentals including variables, data types, functions, packages, control statements, pointers, structs, interfaces, goroutines, channels, and real-world examples with diagrams and best practices.
Go Language Basics - Complete Guide
Go (Golang) is an open-source programming language developed by Google in 2009. It's designed for building scalable, high-performance applications with built-in concurrency support.
Why Go?
- Simple: Clean syntax, easy to learn
- Fast: Compiled to native machine code
- Concurrent: Built-in goroutines and channels
- Efficient: Low memory footprint
- Productive: Fast compilation, great tooling
- Scalable: Powers Docker, Kubernetes, and more
Popular Use Cases
✓ Microservices
✓ Cloud Native Applications
✓ Kubernetes & Docker
✓ REST APIs & gRPC
✓ Distributed Systems
✓ CLI Tools
✓ DevOps Tools
✓ Network Programming
Go vs Other Languages
┌─────────────┬──────────┬──────────┬──────────┐
│ Feature │ Go │ Java │ Python │
├─────────────┼──────────┼──────────┼──────────┤
│ Compilation │ Native │ JVM │Interpreted│
│ Concurrency │ Built-in │ Threads │ Limited │
│ Memory │ Low │ High │ Medium │
│ Speed │ Fast │ Medium │ Slow │
│ Simplicity │ High │ Medium │ High │
└─────────────┴──────────┴──────────┴──────────┘
Hello World
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Run:
go run main.go
Output:
Hello, World!
Compile:
go build main.go
./main
Package System
Every Go file belongs to a package.
// main package - entry point
package main
// library package
package utils
Import Packages
// Single import
import "fmt"
// Multiple imports
import (
"fmt"
"time"
"strings"
)
// Aliased import
import (
f "fmt"
t "time"
)
// Usage
f.Println("Hello")
Package Structure
project/
├── main.go
├── go.mod
├── utils/
│ └── helper.go
└── models/
└── user.go
Variables
Declaration Methods
// Method 1: var with type
var name string = "Venu"
var age int = 30
// Method 2: var with type inference
var name = "Venu"
var age = 30
// Method 3: Short declaration (most common)
name := "Venu"
age := 30
// Multiple variables
var x, y int = 10, 20
a, b := 10, 20
// Zero values
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // ""
Constants
const PI = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
// Typed constants
const MaxUsers int = 100
// iota - auto-incrementing
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
)
Data Types
Basic Types
// Integers
var i8 int8 // -128 to 127
var i16 int16 // -32768 to 32767
var i32 int32 // -2^31 to 2^31-1
var i64 int64 // -2^63 to 2^63-1
var i int // Platform dependent (32 or 64 bit)
// Unsigned integers
var u8 uint8 // 0 to 255
var u16 uint16 // 0 to 65535
var u32 uint32 // 0 to 2^32-1
var u64 uint64 // 0 to 2^64-1
// Floating point
var f32 float32 // IEEE-754 32-bit
var f64 float64 // IEEE-754 64-bit
// Other types
var b bool // true or false
var s string // UTF-8 string
var r rune // Unicode code point (int32)
var by byte // alias for uint8
Type Conversion
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// String conversion
import "strconv"
// Int to string
s := strconv.Itoa(42)
// String to int
i, err := strconv.Atoi("42")
// Float to string
s := strconv.FormatFloat(3.14, 'f', 2, 64)
Operators
Arithmetic Operators
a := 10
b := 3
sum := a + b // 13
diff := a - b // 7
product := a * b // 30
quotient := a / b // 3
remainder := a % b // 1
// Increment/Decrement
a++ // a = a + 1
b-- // b = b - 1
Comparison Operators
a == b // Equal
a != b // Not equal
a < b // Less than
a > b // Greater than
a <= b // Less than or equal
a >= b // Greater than or equal
Logical Operators
true && false // AND - false
true || false // OR - true
!true // NOT - false
Control Statements
If Statement
age := 20
if age >= 18 {
fmt.Println("Adult")
}
// If-else
if age >= 18 {
fmt.Println("Adult")
} else {
fmt.Println("Minor")
}
// If-else if-else
if age < 13 {
fmt.Println("Child")
} else if age < 18 {
fmt.Println("Teenager")
} else {
fmt.Println("Adult")
}
// If with initialization
if num := 10; num > 5 {
fmt.Println("Greater than 5")
}
Switch Statement
day := "Monday"
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 without expression
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("F")
}
// Type switch
var i interface{} = "hello"
switch v := i.(type) {
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
default:
fmt.Println("Unknown type")
}
Loops
Go has only for loop, but it's versatile.
// Traditional for loop
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// While-style loop
i := 0
for i < 5 {
fmt.Println(i)
i++
}
// Infinite loop
for {
fmt.Println("Forever")
break // Exit loop
}
// Range loop (arrays, slices, maps)
nums := []int{1, 2, 3, 4, 5}
for index, value := range nums {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
// Ignore index
for _, value := range nums {
fmt.Println(value)
}
// Only index
for index := range nums {
fmt.Println(index)
}
// Break and continue
for i := 0; i < 10; i++ {
if i == 3 {
continue // Skip 3
}
if i == 7 {
break // Stop at 7
}
fmt.Println(i)
}
Functions
Basic Functions
// Simple function
func greet() {
fmt.Println("Hello!")
}
// Function with parameters
func add(a int, b int) int {
return a + b
}
// Shortened parameter syntax
func multiply(a, b int) int {
return a * b
}
// Multiple return values
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// Named return values
func calculate(a, b int) (sum int, product int) {
sum = a + b
product = a * b
return // Naked return
}
// Usage
result := add(10, 20)
quotient, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
}
Variadic Functions
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
// Usage
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
Anonymous Functions
// Function as variable
add := func(a, b int) int {
return a + b
}
result := add(5, 3)
// Immediately invoked
result := func(a, b int) int {
return a + b
}(5, 3)
Closures
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
// Usage
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3
Arrays and Slices
Arrays (Fixed Size)
// Declaration
var arr [5]int
// Initialization
arr := [5]int{1, 2, 3, 4, 5}
// Auto size
arr := [...]int{1, 2, 3, 4, 5}
// Access
fmt.Println(arr[0]) // 1
// Length
fmt.Println(len(arr)) // 5
// Iterate
for i, v := range arr {
fmt.Printf("Index: %d, Value: %d\n", i, v)
}
Slices (Dynamic Size)
// Create slice
nums := []int{1, 2, 3, 4, 5}
// Make slice
nums := make([]int, 5) // length 5
nums := make([]int, 5, 10) // length 5, capacity 10
// Append
nums = append(nums, 6)
nums = append(nums, 7, 8, 9)
// Slicing
slice := nums[1:4] // Elements 1, 2, 3
slice := nums[:3] // First 3 elements
slice := nums[2:] // From index 2 to end
// Length and capacity
fmt.Println(len(nums)) // Length
fmt.Println(cap(nums)) // Capacity
// Copy
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
Maps
Key-value data structure.
// Create map
ages := map[string]int{
"Venu": 30,
"John": 25,
}
// Make map
ages := make(map[string]int)
// Add/Update
ages["Alice"] = 28
// Get value
age := ages["Venu"]
// Check existence
age, exists := ages["Bob"]
if exists {
fmt.Println("Age:", age)
} else {
fmt.Println("Not found")
}
// Delete
delete(ages, "John")
// Iterate
for name, age := range ages {
fmt.Printf("%s is %d years old\n", name, age)
}
// Length
fmt.Println(len(ages))
Structs
Go's way of creating custom types.
// Define struct
type Person struct {
Name string
Age int
Email string
Address Address
}
type Address struct {
Street string
City string
Zip string
}
// Create instance
person := Person{
Name: "Venu",
Age: 30,
Email: "[email protected]",
}
// Access fields
fmt.Println(person.Name)
person.Age = 31
// Anonymous struct
point := struct {
X int
Y int
}{10, 20}
// Struct embedding (composition)
type Employee struct {
Person // Embedded struct
ID int
Salary float64
}
emp := Employee{
Person: Person{Name: "Venu", Age: 30},
ID: 1001,
Salary: 150000,
}
// Access embedded fields
fmt.Println(emp.Name) // Direct access
Methods
Functions associated with types.
type Rectangle struct {
Width float64
Height float64
}
// Value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Pointer receiver (can modify)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
// Usage
rect := Rectangle{Width: 10, Height: 5}
fmt.Println("Area:", rect.Area()) // 50
rect.Scale(2)
fmt.Println("New area:", rect.Area()) // 200
Interfaces
Define behavior, not implementation.
// Define interface
type Shape interface {
Area() float64
Perimeter() float64
}
// Implement for Circle
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.Radius
}
// Implement for Rectangle
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Use interface
func printShapeInfo(s Shape) {
fmt.Printf("Area: %.2f\n", s.Area())
fmt.Printf("Perimeter: %.2f\n", s.Perimeter())
}
// Usage
circle := Circle{Radius: 5}
rect := Rectangle{Width: 10, Height: 5}
printShapeInfo(circle)
printShapeInfo(rect)
// Empty interface (any type)
var i interface{}
i = 42
i = "hello"
i = true
Pointers
Store memory addresses.
// Create pointer
x := 10
p := &x // Address of x
// Dereference
fmt.Println(*p) // Value at address (10)
// Modify through pointer
*p = 20
fmt.Println(x) // 20
// Pointer to struct
type Person struct {
Name string
Age int
}
person := Person{Name: "Venu", Age: 30}
ptr := &person
// Access fields
fmt.Println(ptr.Name) // Automatic dereferencing
(*ptr).Age = 31
// Function with pointer
func increment(n *int) {
*n++
}
num := 5
increment(&num)
fmt.Println(num) // 6
Pointer vs Value
Value Receiver:
┌──────────┐
│ Original │ (unchanged)
└──────────┘
│
▼
┌──────────┐
│ Copy │ (modified)
└──────────┘
Pointer Receiver:
┌──────────┐
│ Original │ (modified)
└──────────┘
▲
│
┌──────────┐
│ Pointer │
└──────────┘
Error Handling
Go uses explicit error handling.
import (
"errors"
"fmt"
)
// Return error
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Custom error
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
// Usage
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
// Error wrapping (Go 1.13+)
import "fmt"
err := fmt.Errorf("failed to process: %w", originalErr)
// Check wrapped error
if errors.Is(err, originalErr) {
// Handle specific error
}
Goroutines
Lightweight threads managed by Go runtime.
import (
"fmt"
"time"
)
// Simple goroutine
func sayHello() {
fmt.Println("Hello from goroutine!")
}
func main() {
go sayHello() // Run concurrently
time.Sleep(time.Second) // Wait for goroutine
}
// Anonymous goroutine
go func() {
fmt.Println("Anonymous goroutine")
}()
// Multiple goroutines
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println("Goroutine", n)
}(i)
}
Goroutine Flow
Main Thread
│
├─── Goroutine 1
│
├─── Goroutine 2
│
└─── Goroutine 3
Channels
Communication between goroutines.
// Create channel
ch := make(chan int)
// Buffered channel
ch := make(chan int, 10)
// Send to channel
ch <- 42
// Receive from channel
value := <-ch
// Close channel
close(ch)
// Example: Producer-Consumer
func producer(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("Produced:", i)
}
close(ch)
}
func consumer(ch chan int) {
for value := range ch {
fmt.Println("Consumed:", value)
}
}
func main() {
ch := make(chan int)
go producer(ch)
consumer(ch)
}
// Select statement (multiple channels)
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
case <-time.After(time.Second):
fmt.Println("Timeout")
}
Defer, Panic, Recover
Defer
Executes function at end of surrounding function.
func readFile() {
file, err := os.Open("file.txt")
if err != nil {
return
}
defer file.Close() // Executed when function returns
// Read file...
}
// Multiple defers (LIFO order)
defer fmt.Println("First")
defer fmt.Println("Second")
defer fmt.Println("Third")
// Output: Third, Second, First
Panic
Stop normal execution.
func checkAge(age int) {
if age < 0 {
panic("age cannot be negative")
}
}
Recover
Regain control after panic.
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from:", r)
}
}()
panic("something went wrong")
fmt.Println("This won't execute")
}
Real-World Examples
HTTP Server
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func getUsers(w http.ResponseWriter, r *http.Request) {
users := []User{
{ID: 1, Name: "Venu", Email: "[email protected]"},
{ID: 2, Name: "John", Email: "[email protected]"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func main() {
http.HandleFunc("/users", getUsers)
fmt.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Concurrent File Processing
package main
import (
"fmt"
"sync"
)
func processFile(filename string, wg *sync.WaitGroup) {
defer wg.Done()
// Simulate processing
fmt.Printf("Processing %s\n", filename)
}
func main() {
files := []string{"file1.txt", "file2.txt", "file3.txt"}
var wg sync.WaitGroup
for _, file := range files {
wg.Add(1)
go processFile(file, &wg)
}
wg.Wait() // Wait for all goroutines
fmt.Println("All files processed")
}
Worker Pool
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// Start 3 workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Send 9 jobs
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// Collect results
for a := 1; a <= 9; a++ {
<-results
}
}
Best Practices
Code Organization
// ✅ Good: Clear package structure
project/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handlers/
│ ├── models/
│ └── services/
├── pkg/
│ └── utils/
└── go.mod
// ✅ Good: Small, focused functions
func getUserByID(id int) (*User, error) {
// Single responsibility
}
// ❌ Avoid: Large, multi-purpose functions
Error Handling
// ✅ Good: Check errors immediately
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
// ❌ Avoid: Ignoring errors
result, _ := doSomething()
Concurrency
// ✅ Good: Use WaitGroup for synchronization
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
process(n)
}(i)
}
wg.Wait()
// ✅ Good: Use channels for communication
ch := make(chan int)
go producer(ch)
consumer(ch)
Common Patterns
Singleton
import "sync"
type Database struct {
// fields
}
var (
instance *Database
once sync.Once
)
func GetInstance() *Database {
once.Do(func() {
instance = &Database{}
})
return instance
}
Builder Pattern
type Server struct {
host string
port int
timeout time.Duration
}
type ServerBuilder struct {
server *Server
}
func NewServerBuilder() *ServerBuilder {
return &ServerBuilder{server: &Server{}}
}
func (b *ServerBuilder) Host(host string) *ServerBuilder {
b.server.host = host
return b
}
func (b *ServerBuilder) Port(port int) *ServerBuilder {
b.server.port = port
return b
}
func (b *ServerBuilder) Build() *Server {
return b.server
}
// Usage
server := NewServerBuilder().
Host("localhost").
Port(8080).
Build()
Testing
// math.go
package math
func Add(a, b int) int {
return a + b
}
// 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)
}
}
// Table-driven tests
func TestAddTable(t *testing.T) {
tests := []struct {
a, b, expected int
}{
{2, 3, 5},
{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)
}
}
}
// Run tests
// go test
// go test -v
// go test -cover
Summary
Go is designed for modern, scalable applications with these key features:
✅ Simple Syntax - Easy to learn and read ✅ Fast Compilation - Quick build times ✅ Built-in Concurrency - Goroutines and channels ✅ Strong Standard Library - Batteries included ✅ Static Typing - Catch errors at compile time ✅ Great Tooling - go fmt, go test, go mod
Key Concepts Mastered
- Variables and data types
- Functions and methods
- Structs and interfaces
- Slices and maps
- Pointers
- Error handling
- Goroutines and channels
- Defer, panic, recover
Next Steps
- Build REST APIs with
net/http - Learn database integration (
database/sql) - Explore popular frameworks (Gin, Echo)
- Master testing and benchmarking
- Study concurrency patterns
- Build microservices
Remember: Go's simplicity is its strength. Write clear, idiomatic Go code and leverage its powerful concurrency features! 🚀