Full Stack • Java • System Design • Cloud • AI Engineering

Go2026-06-07

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

  1. Build REST APIs with net/http
  2. Learn database integration (database/sql)
  3. Explore popular frameworks (Gin, Echo)
  4. Master testing and benchmarking
  5. Study concurrency patterns
  6. Build microservices

Remember: Go's simplicity is its strength. Write clear, idiomatic Go code and leverage its powerful concurrency features! 🚀