Contents

Golang in Brief

In a nutshell about everything in golang.

Note
This is a very brief summary. If you want to get a basic understanding of Golang, check out the Golang tutorial.

Go is a compiled, statically typed programming language developed by Google. The first stable version was released in 2012.

1. Declaring Variables

Explicit Declaration (var keyword):

var car string
var name string = "John"
var age int = 25
var isStudent bool = false

Implicit Declaration (:= operator):

// Possible in inner function
name := "Alice"  // Type inferred as string
age := 30        // Type inferred as int
isStudent := true // Type inferred as bool

A variable name is an arbitrary identifier consisting of letters, numbers, and the underscore character (_). And do not include reserved words.

Private variables start with a lowercase letter and are accessible only from within the package, public variables start with an uppercase letter and are accessible from other packages.

2. Basic Types in Go

2.1. Primitive Types

TypeDescriptionMin/Max ValueDefault
boolBoolean (true, false)false / truefalse
stringImmutable sequence of characters"" / N/A""
intInteger (platform-dependent)-2^63 to 2^63-1 (64-bit)0
int8-128 to 1270
int16-32,768 to 32,7670
int32-2,147,483,648 to 2,147,483,6470
int64-2^63 to 2^63-10
uintUnsigned Integer (p.- d.)0 to -2^64 (64-bit)0
uint80 to 2550
uint160 to 65,5350
uint320 to 4,294,967,2950
uint640 to 2^64 - 10
float32, float64Floating-point numbers~2.3E-308 to ~1.8E3080.0
complex64, complex128Complex numbersN/A(0+0i)
byteRepresents a byte (alias uint8)0 to 2550
runeUnicode character (alias uint32)0 to 2^31-10

2.2. Composite Types

TypeDescriptionSize (bytes)Default
arrayFixed-size collectionDepends on element type & length[0,0,...]
sliceDynamic-size collection (backed by array)24 (on 64-bit)nil
mapKey-value storageVaries (depends on keys/values)nil
structCustom data type (grouping fields)Sum of field sizes (with padding){}
pointerStores memory address of a variable4 (32-bit) / 8 (64-bit)nil
interfaceDefines behavior, not data16 (on 64-bit)nil

3. Pointers

Pointer manipulation:

var p *int // create pointer
a := 2
p = &a
*p++
fmt.Println(*p) // value (3)
fmt.Println(&p) // address (0xc0000a2038)

4. Constants (const keyword)

Constants have immutable values and must be initialized at the time of declaration:

const Pi float64 = 3.14159
const Greeting = "Hello, Go!"

Numerations:

const (
  Sunday = iota // 0
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday  // 6
  _         // skip 7
  New       // 8
)

5. Logical operation

OperatorNameDescription
&&ANDReturns true if both conditions are true
||ORReturns true if at least one condition is true
!NOTNegates a boolean expression

6. Control Flow Keywords

KeywordPurpose
fallthroughForces execution of the next case in a switch, ignoring conditions.
continueSkips the current iteration of a loop and moves to the next one.
breakExits a loop or switch statement immediately.
returnExits a function and optionally returns values.
gotoJumps to a labeled statement (use sparingly).
deferDefers execution of a function until the surrounding function exits.
panicTriggers an error and stops execution.
recoverCatches a panic to prevent program termination.

7. Go Creation & Manipulation Operators

FunctionPurpose
new(T)Allocates memory for type T, returns a pointer to T. p := new(int)
make(T, size, cap)Creates slices, maps, or channels with a given size and capacity.
append(slice, elems...)Adds elements to a slice, returning a new slice if capacity changes.
copy(dst, src)Copies elements from src to dst, returns the number of elements copied.
len(container)Returns the length of a slice, map, array, or string.
cap(slice)Returns the capacity of a slice.
delete(map, key)Removes a key from a map.

8. Custom Types (type keyword)

Golang allows defining new types:

type Age int
var myAge Age = 25

9. Composite Types (Collections & Structures)

Group multiple values using arrays, slices, maps, structs, and pointers.

9.1 Arrays (Fixed Size)

Arrays have a fixed length and store elements of the same type:

var numbers [3]int = [3]int{10, 20, 30}
fmt.Println(numbers[0]) // Output: 10

// Shorthand:
numbers := [...]int{1, 2, 3} // Compiler infers size

9.2 Slices (Dynamic Size)

Slices are more flexible than arrays and contain:

  • capacity,
  • length,
  • pointer to an underlying array.
var slice1 = make([]int, 4, 7) // Set len(), cap()
nums := []int{10, 20, 30} // Slice with 3 elements
slice4 := array[:] // Convert array to slice
nums = append(nums, 40) // Expands the slice

Truncate slice:

baseArray := [8]string{"Anna", "Max", "Eva", "Leo", "Nina", "Tom", "Sophie", "Chris"} // the base array

slice1 := baseArray[1:5] // from 2 to 5 element [Max Eva Leo Nina]
slice2 := baseArray[:3]  // from 1 to 3 element [Anna Max Eva]
slice3 := baseArray[4:]  // to 5 [Nina Tom Sophie Chris]

Remove element from slice:

a := []int{1, 2, 3, 4, 5, 6, 7}
a = append(a[0:2], a[3:]...)
fmt.Println(a) // [1 2 4 5 6 7]

9.3 Maps (Key-Value Pairs)

A map stores values using a key: map is a reference to a hash table.

person := map[string]int{"Alice": 25, "Bob": 30}
fmt.Println(person["Alice"]) // Output: 25

Check key in the map:

if value, ok := m[1]; ok {
 fmt.Println(value) // key exist
}

9.4 Structures (Custom Data Types)

Allow grouping different types into a single unit:

type Person struct {
  Name string
  Age int
}
p := Person{Name: "John", Age: 30}
fmt.Println(p.Name) // Output: John

Anonymous structure:

student := struct {
  name string
  id   int64
  age  int32
}{
  name: "Julia",
  id:   12345,
  age:  30,
}

10. String

String it’s the immutable sequence of characters. But string maybe representation as []byte or []rune and modifying. Rune (alias int32) determinate a symbol (1-4 bytes) for support UTF-8.

11. Format Specifiers for fmt.Printf

FormatDescription
%vDefault format for the value
%+vStruct with field names
%#vGo syntax representation
%TType of the value
%%Literal %
%dDecimal
%bBinary
%oOctal
%xHexadecimal (lowercase)
%XHexadecimal (uppercase)
%cCharacter representation
%UUnicode format
%fDecimal (default precision 6)
%.2fFixed decimal (2 places)
%eScientific notation (lower)
%EScientific notation (upper)
%gCompact float (auto e or f)
%GCompact float (upper E)
%sString
%qQuoted string
%xHex dump (lowercase)
%XHex dump (uppercase)
%tBoolean
%pPointer address

12. Function

In Go, parameters are passed to a function by value, meaning a copy of them is passed. Changes to parameters inside the function do not affect the original variables. Including the link.

Closure is an anonymous function that captures and remembers the variables from its surrounding scope:

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    inc := counter() // Creates a closure
    fmt.Println(inc()) // 1
    fmt.Println(inc()) // 2
    fmt.Println(inc()) // 3
}

Function of constructor:

func newProduct(name, category string, price float64)
 *Product {
     return &Product{name, category, price}
}

13. Interface (Dynamic Type)

Interfaces define behavior without specifying exact types:

type Speaker interface {
  MetaSpeakerInterface
  Speak() string
}
type Dog struct{}
  func (d Dog) Speak() string {
  return "Woof!"
}

var animal Speaker = Dog{}
fmt.Println(animal.Speak()) // Output: Woof!

Find type of variable:

func display(a interface{}) {
  // Using type switch
  switch a.(type) {
  case int, uint: //задаем два условия
    fmt.Printf("Type:%T -- Value:%v\n", a, a.(int))
  case string:
    fmt.Printf("Type:%T -- Value:%v\n", a, a.(string))
  case float64:
    fmt.Printf("Type:%T -- Value:%v\n", a, a.(float64))
  default:
    fmt.Println("Type not found")
  }
}

14. Error

To create an error, you need to implement the interface:

type error interface {
 Error() string
}

The other possible ways of creation:

err := errors.New("my error") // Create error
eff := fmt.Errorf("other error")
panic("division by zero!") // Generate error and exit
// defer - recover - panic (banch)

15. Defer, panic and recover

In together:

func safeFunction() {
    defer func() {
        if r := recover(); r != nil { // Handle panic
            fmt.Println("Recovered:", r)
        }
    }()

    fmt.Println("Before panic")
    panic("Unexpected error!")  // Panic occurs
    fmt.Println("After panic")  // This will not run
}

16. Goroutines

Goroutines are lightweight threads in Go that allow concurrent execution of functions. Unlike traditional threads, they are managed by the Go runtime, making them efficient and scalable.

16.1. Basics of Goroutines

You can start a new goroutine using the go keyword before a function call.

Running a Function as a Goroutine:

package main

import (
  "fmt"
  "time"
)

func sayHello() {
  fmt.Println("Hello from goroutine!")
}

func main() {
  go sayHello() // Starts a new goroutine
  fmt.Println("Hello from main function!")

    time.Sleep(1 * time.Second) // Wait for goroutine to finish

// Output (Order may vary):
// Hello from main function!
// Hello from goroutine!
}

16.2. Running Multiple Goroutines

Create a Goroutines in cycle:

package main

import (
  "fmt"
  "time"
)

func printNumber(n int) {
  fmt.Println("Number:", n)
}

func main() {
  for i := 1; i <= 5; i++ {
  go printNumber(i) // Each call runs in a new goroutine
  }
    time.Sleep(1 * time.Second) // Prevent main from exiting early
}

16.3. Synchronization with sync.WaitGroup

Using sync.WaitGroup to Wait for Goroutines:

package main

import (
  "fmt"
  "sync"
)

func worker(id int, wg *sync.WaitGroup) {
  fmt.Printf("Worker %d started\n", id)
  wg.Done() // Decrement WaitGroup counter
}

func main() {
  var wg sync.WaitGroup

  for i := 1; i <= 3; i++ {
    wg.Add(1) // Increment counter
    go worker(i, &wg)
  }

  wg.Wait() // Block until all goroutines finish
  fmt.Println("All workers finished")
}

16.4. Communication Between Goroutines (Channels)

Creating and Using a Channel:

package main

import "fmt"

func sendMessage(ch chan string) {
  ch <- "Hello from Goroutine!" // Send data into channel
}

func main() {
  ch := make(chan string) // Create a channel

  go sendMessage(ch)

  msg := <-ch // Receive data from channel
  fmt.Println(msg)
}

16.5. Buffered vs. Unbuffered Channels

Unbuffered Channels (default) block until the data is received. Buffered Channels allow storing messages.

Buffered Channel Example:

ch := make(chan int, 3) // Buffer size of 3
ch <- 1
ch <- 2
ch <- 3
fmt.Println(<-ch) // Output: 1

16.6. Closing a Channel

   ch := make(chan int)

go func() {
  for i := 1; i <= 5; i++ {
  ch <- i
}
  close(ch) // Close channel after sending
}()

for num := range ch {
  fmt.Println(num) // Reads until channel is closed
}

16.7. Select Statement (Handling Multiple Channels)

The select statement lets a goroutine wait on multiple channels:

package main

import (
  "fmt"
  "time"
)

func main() {
  ch1 := make(chan string)
  ch2 := make(chan string)

  go func() {
      time.Sleep(1 * time.Second)
      ch1 <- "Message from channel 1"
  }()

  go func() {
      time.Sleep(2 * time.Second)
      ch2 <- "Message from channel 2"
  }()

  select {
  case msg1 := <-ch1:
      fmt.Println(msg1)
  case msg2 := <-ch2:
      fmt.Println(msg2)
  case <-time.After(3 * time.Second):
      fmt.Println("Timeout!")
  }

}

16.8. Worker Pool Pattern (Efficient Goroutine Management)

import (
  "fmt"
  "sync"
  "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
  for j := range jobs {
    fmt.Printf("Worker %d processing job %d\n", id, j)
    time.Sleep(time.Second)
    results <- j * 2
  }
}

func main() {
  jobs := make(chan int, 5)
  results := make(chan int, 5)

  var wg sync.WaitGroup

  for w := 1; w <= 3; w++ {
    wg.Add(1)
    go func(id int) {
      worker(id, jobs, results)
      wg.Done()
    }(w)
  }

  for j := 1; j <= 5; j++ {
    jobs <- j
  }
  close(jobs)

  wg.Wait()
  close(results)

  for res := range results {
    fmt.Println("Result:", res)
  }
}

16.9 Best Practices for Goroutines

  • Always use a sync.WaitGroup when managing multiple goroutines.
  • Use channels for safe communication instead of shared memory.
  • Avoid memory leaks by ensuring all goroutines finish execution.
  • Limit goroutines when handling many tasks using worker pools.
  • Use context.Context for cancellation of long-running goroutines.

17. Handling Race Conditions with sync.Mutex

When multiple goroutines access shared data, race conditions can occur. The sync.Mutex (mutual exclusion) helps prevent this.

package main

import (
  "fmt"
  "sync"
  "time"
)

var counter = 0
var mu sync.Mutex // Mutex for safe access

func increment() {
  for i := 0; i < 1000; i++ {
  mu.Lock() // Lock access
  counter++ // Safe update
  mu.Unlock() // Unlock
  }
}

func main() {
  for i := 0; i < 5; i++ {
    go increment()
  }

  time.Sleep(time.Second)
  fmt.Println("Final Counter:", counter) // Correct result
}

18. Read-Write Synchronization with sync.RWMutex

If you have multiple readers but only one writer, use sync.RWMutex:

package main

import (
  "fmt"
  "sync"
  "time"
)

var value = 0
var rw sync.RWMutex // Read-Write Mutex

func readValue(id int) {
  rw.RLock() // Lock for reading
  fmt.Printf("Reader %d reads value: %d\n", id, value)
  rw.RUnlock()
}

func writeValue(newValue int) {
  rw.Lock() // Lock for writing
  value = newValue
  fmt.Printf("Writer updated value to %d\n", value)
  rw.Unlock()
}

func main() {
  go readValue(1)
  go readValue(2)
  go writeValue(42)
  go readValue(3)

  time.Sleep(time.Second)
// Multiple readers can read concurrently, but only one writer can write.
}

19. Goroutine Cancellation with context.Context

Goroutines don’t stop automatically when main exits. Use context to control them.

Cancelling a Goroutine:

package main

import (
  "context"
  "fmt"
  "time"
)

func worker(ctx context.Context) {
  for {
    select {
      case <-ctx.Done():
        fmt.Println("Worker stopped")
        return
      default:
        fmt.Println("Working...")
        time.Sleep(500 * time.Millisecond)
    }
  }
}

func main() {
  ctx, cancel := context.WithCancel(context.Background())

  go worker(ctx)

  time.Sleep(2 * time.Second)
  cancel() // Send cancellation signal

  time.Sleep(time.Second) // Wait to see output
}

20. Using context.WithTimeout for Timeouts

Automatically cancel goroutines after a timeout:

package main

import (
  "context"
  "fmt"
  "time"
)

func worker(ctx context.Context) {
  for {
    select {
      case <-ctx.Done():
        fmt.Println("Timeout reached, worker stopping")
        return
      default:
        fmt.Println("Worker is running...")
        time.Sleep(500 * time.Millisecond)
    }
  }
}

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
defer cancel() // Clean up

    go worker(ctx)

    time.Sleep(3 * time.Second) // Ensure timeout occurs

}

21. Using sync.Once for One-Time Initialization

Use sync.Once to ensure a function runs only once, even if called multiple times:

package main

import (
 "fmt"
 "sync"
)

var once sync.Once

func initConfig() {
 fmt.Println("Initializing config...")
}

func main() {
 wg := sync.WaitGroup{}

 for i := 0; i < 3; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   once.Do(initConfig) // Run once
  }()
 }

 wg.Wait()
}

22. Using sync.Cond for Goroutine Coordination

sync.Cond lets goroutines wait for a condition before proceeding:

package main

import (
  "fmt"
  "sync"
  "time"
)

var cond = sync.NewCond(&sync.Mutex{})
var ready = false

func worker(id int) {
  cond.L.Lock()
  for !ready {
    cond.Wait() // Wait for signal
  }
  fmt.Printf("Worker %d is running\n", id)
  cond.L.Unlock()
}

func main() {
  for i := 1; i <= 3; i++ {
    go worker(i)
  }

  time.Sleep(time.Second)
  cond.L.Lock()
  ready = true
  cond.Broadcast() // Notify all waiting goroutines
  cond.L.Unlock()

  time.Sleep(time.Second)
}

23. Best Practices for Goroutines synchronization

  • Use sync.WaitGroup to wait for goroutines to complete.
  • Use sync.Mutex or sync.RWMutex to prevent race conditions.
  • Use channels for safe communication between goroutines.
  • Use context.Context for controlled cancellation.
  • Use worker pools to manage goroutines efficiently.
  • Use sync.Once for single-time execution.