Learning Golang
I’ve been primarily a Python developer for the past decade. A job application has asked for the coding interview to be in Golang. This post is to build out my reference in using Go as a Python dev and some sub-projects to get used to Go and Go’s concurrency model.
Table of Contents
Python Differences
Mostly from here
Variables
# Python
some_variable = 2
SOME_CONST = 3.14
# Two variables at once
a, b = 1, "one"
// Go
var someVariable int = 2
const SomeCost float = 3.14
var a, b = 1, "one" // Type is inferred
For Loops
# Python
for i in range(10):
print(i)
// Golang
// Initial; Condition; After
for i := 0; i < 10; i++ {
fmt.Println(i)
}
While Loops
# Python
counter = 0
while counter < 5:
print(counter)
counter += 1
// Golang
var counter int = 0 // or counter := 0
for counter < 5 {
fmt.Println(counter)
counter += 1
}
Arrays/Slices
# Python
my_list = [False, 1, "Two"]
# Golang
// NOTE: Arrays length is part of its type
var boolArray [3]bool = [3]bool{false, true, true}
var stringArray [3]string = [3]string{"Zero", "one", "two"}
// Slice is dynamic view of an array
var boolSlice []bool = []bool{false} // Var variableName []type. Supporting array created automatically
The length and capacity can be got through len(s)
and cap(s)
Creating a slice with make
Slices can be created with the built in make function. This is how you create dynamically sized arrays
a := make([]int, 5) // len(a) == 5
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
Appending to slices:
var s []int
s = append(s, 0) // Append works on nil slices. Array will be grown as needed
s = append(s, 2, 3, 4) // Can append multiple values at once.
For loops over containers
# Python
my_list = [False, 1, "two"]
my_dict = {"hello": 0, "world": 1}
for item in my_list:
print(item)
for key, val in enumerate(my_dict):
print(f"{key}: {val}")
// Go
var intSlice []int = []int{1,2,3}
for index, value := range intSlice {
fmt.Println(index, value)
}
var myMap map[string]int = map[string]int{"hello": 1, "world": 2}
for key, value := range myMap {
fmt.Println(key, value)
}
Maps
my_dict = {"hi": 1, "bye": False}
print(my_dict["hi"])
// Go
var theMap[string]int = map[string]int{"hi": 1, "bye": 2}
fmt.Println(theMap["hi"])
// Making, accessing, deleting, and retrieving from a map
m = make(map[string]int)
m["answer"] = 42
delete(m, "answer")
v, ok := m["answer"]
Functions
# Python
def the_function(first_arg, second_arg) -> Tuple[str, bool]:
return f"The first arg is {first_arg}, second is {second_arg}", True
// Go
func theFunction(firstArg string, secondArg string) (string, bool) {
return fmt.Sprintf("First arg is %s, second is %s", firstArg, secondArg), true
}
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // "Naked" return, returns named return values.
}
// NOTE: theFunction is private, TheFunction is exported
If Statements
if ok := someFunction(x, y); ok {
// We can have an initializer here, valid for the scope of the if
}
Switch
- Does not need explicit breaks (always there)
- Need not be constants. Values need not be integers.
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
Basic Types
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
Pointers
i := 42
p = &i // p points to i
fmt.Println(*p) // Read i through the pointer p
*p = 21 // Set i through pointer.
Structs
type Vertex struct {
X, Y int
}
func main() {
fmt.Println(Vertex{1, 2})
v := Vertex{X: 1} // y is implicitly 0
v.X = 4 // Fields are accessed using a dot.
// Structs can be accessed through a struct pointer
p := &v
p.X = 1e9 // Language permits just using dot operator without needing to deference
p = &Vertex{1, 2} // Has a type of *Vertex
}
Function values
// Example shows fn variable requiring a function pointer
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
// hypot is a function closure, can access variables in the declared scope
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
OOP
Methods
type Vertex struct {
X, Y float64
}
// Operates on a "copy" of a vertex. Needs to be pointer if doing the object itself
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
Interfaces
defined as a set of method signatures
Errors
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
Generics
Type Parameters
// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
// v and x are type T, which has the comparable
// constraint, so we can use == here.
if v == x {
return i
}
}
return -1
}
Generic Types
// List represents a singly-linked list that holds
// values of any type.
type List[T any] struct {
next *List[T]
val T
}
Go Packages
In Python, packages are in a directory with init.py.
Go requires a go.mod file
go mod init myproject
creates the following
module myproject
go 1.25.0
Concurrency
Defering
- A stack of “deferred statements” is kept until the surrounding function returns
- Useful for closing files, freeing mutexes, etc, etc.
func main() {
defer fmt.Println("World")
fmt.Println("Hello")
}
Channels
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
Select
- Allows us to wait on multiple communications operations
// C and quit are both of type "chan int"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
// We're putting x into c
case c <- x:
x, y = y, x+y
// When quit is available, we exit out of this forever loop
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
Waitgroups
Allows us to wait for multiple Goroutines to finish
import "sync"
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Go(func() {
worker(i)
})
}>
Locks and Semaphores
While there are no explicit semaphore, we can use a buffered channel
// Semaphore represents a counting semaphore.
type Semaphore chan struct{}
// Acquire attempts to acquire a slot from the semaphore.
func (s Semaphore) Acquire() {
s <- struct{}{} // Send an empty struct to acquire a slot
}
// Release releases a slot back to the semaphore.
func (s Semaphore) Release() {
<-s // Receive from the channel to release a slot
}
func main() {
maxConcurrency := 3
sem := make(Semaphore, maxConcurrency) // Create a semaphore with a capacity of 3
...
}
Mutex
import "sync"
var mu = sync.Mutex{}
defer mu.Unlock()
mu.Lock()
Atomic counters
import "sync/atomic"
var ops atomic.Uint64
ops.Add(1)
Practice Apps
To practice concurrency and get more comfortable with Go. Here are some example apps.
- First Runthrough of basic nc chat server . I felt like this was abusing locks though, so,
- Second Runthrough of basic nc chat server.
- gRPC tui chat app using Bubble Tea.