The Hitchhiker’s Guide to Go

The Go Gopher; Go’s mascot.

This blogpost introduces you to the Go programming language.

It is mostly a summary of “The Go Programming Language” book, by Alan A. A. Donovan, and Brian W. Kernighan, with examples from the tour of Go.


Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.

Conceived at Google in 2007 and announced in 2009 Go is a compiled, strongly, statically typed language. It is primarily influenced by the C language.

Go is an object-based language, but strictly speaking not an object-oriented one. There is the notion of objects but there are no classes as such or hierarchies; it relies on composition but doesn’t offer extensibility beyond duck typing.

The language has become very popular over the last years, especially in the platform and infrastructure space. As an example, the core components behind Docker, Kubernetes, and Istio are written in Go.

Features and limitations

The following table provides an overview of useful features but also limitations of the language.

PS: Note that this list refers to Go 1.16. For example, there is now an accepted proposal for adding generics to the language.

Features and limitations of the Go Programming language.


You can install Go by following the official docs. At the time of writing this article Go 1.16.2 is the latest version. You can find all the release binaries here.

Program Structure


  • Packages in Go serve the same purpose as libraries or modules in other languages (example).
  • A simple rule governs which identifiers are exported and which are not; exported identifiers start with a capital letter (also see Names).


  • Import statements can take the form of multiple import statements or parenthesized ones (example).
  • Every package is identified by its import path (e.g.
  • Each package has a name, which is usually the last part of its import path (e.g. kubernetes in the previous example).


  • Visibility; The case of the first letter of a name determines its visibility across package boundaries. If the name begins with a capital letter, it is exported, meaning that it is visible and accessible outside its own package (example).
  • Length; In contrast to other languages like Java, the convention in Go is to use short names (Russ Cox explains his philosophy here).
  • Case style; Camel case is preferred when combining words.


  • Variables are declared using the var statement, e.g. var i int = 1 (example).
  • There are various ways for initializing variables (example).
  • The language supports short variable declarations, e.g. i := 1 (example).


  • Constants are expressions whose value is known to the compiler and whose evaluation is guaranteed to occur at compile time rather than at runtime (example1, example2).

Data Types

Basic types

  • Numbers (e.g. integers, floating-point numbers)
  • Strings
  • Booleans; true/false. Note that there is no implicit conversion from a boolean value to a numeric value like 0 or 1.
  • Example

Aggregate types


  • Also a composite type.
  • Arrays are rarely used in Go due to their fixed length. Slices are preferred instead.
  • Example


  • Also a composite type.
  • Structs group together zero or more named values of arbitrary types as a single entity. Each value in a struct is called a field.
  • The name of a struct field is exported if it begins with a capital letter.
  • Structs usually appear within the declaration of a named type.
  • A struct may declare a field of pointer types which lets you create recursive data structures like linked lists and trees.
  • The struct type with no fields is called an empty struct (struct{}). It can be used to represent a set (e.g. map[string]struct{}).
  • Go lets you declare a field with a type but no name; such fields are called anonymous fields.
  • Example

Reference types


  • A pointer is the address of a variable, the location at which a value is stored (example).
  • Each time you take the address of a variable or copy a pointer you create new aliases or ways to identify the same variable.


  • Also a composite type.
  • Slices represent variable-length sequences whose elements have the same type.
  • A slice has 3 components; a pointer a length, and a capacity.
  • The built-in function make creates a slice of a specified element type, length, and capacity.
  • Slices are mutable.
  • Example


  • Also a composite type
  • A map is a reference to a hash table.
  • All of the keys should be of the same type. The same goes for values.
  • The built-in function make creates a map.
  • Maps are mutable.
  • A nil map reference behaves like an empty map. But storing to a nil map causes a panic.
  • A set can be simulated using a map (e.g. map[string]bool — set of strings)
  • The value type of a map can be a composite type, such as a map or a slice.
  • Example


  • A function declaration in Go has a name, a list of parameters, an optional list of results, and a body (example).
  • Functions in Go are first-class citizens (example).
  • Go doesn’t support default parameter values.
  • Arguments are passed by value; this means the function receives a copy of each argument and any modifications to the copy do not affect the caller.
  • However, by passing a reference (e.g. a pointer, slice, map, function, or channel) the caller may be affected.
  • Functions may be recursive. Go uses variable-size stacks which allow using recursion safely.
  • Go support anonymous and variadic functions.


  • We talk about channels extensively later on!

Interface types

  • An interface type is defined as a set of method signatures (example).
  • In Go, interfaces are implemented implicitly, i.e. there is no implements keyword (example). A type satisfies an interface if it possess all the methods the interface requires!
  • The interface type that specifies zero methods is known as the empty interface. An empty interface may hold values of any type (example)
  • Remember; not everything needs to be an object! Interfaces are only needed when there are 2 or more concrete types that must be dealt with in a uniform way.


Go does not have classes. However, you can define methods on types/receivers this way simulating a class (example).

Methods can be associated with any type. This enables defining additional behaviors for simple types such as numbers and strings and allows for shorter method names (since the package name is omitted).

Method receivers

  • Example
  • No special name like this or self is used for the receiver; we choose receiver names just as we would for any other language.
  • If a function needs to update a variable, a pointer receiver can be passed (remember, function arguments are passed by value).


  • Cross-package visibility of variables and methods is controlled through capitalization of names; a method name that starts with a capital letter is exported and is accessible from other packages.
  • Encapsulating an object requires a struct; capitalizing the struct makes it accessible from other packages.

Error handling

Go has a unique approach towards error handling. It is based on the distinction between expected (errors) and unexpected (panics) failures.

A function for which failures are expected (e.g. one that performs I/O operations) returns an additional result, conventionally the last one. If the failure has only one possible cause, the result is a boolean, usually called ok.


The authors of “The Go Programming Language” book list the following strategies for error handling:

  • Log the error and continue (e.g. log.Printf() or fmt.Fprintf()).
  • Propagate the error (e.g. fmt.Errorf()).
  • Retry.
  • Stop the program gracefully (e.g. os.Exit(1) or log.FatalF()).

Defer, Panic, and Recover

This article explains the concepts of Defer, Panic, and Recover extensively. Below you can find a brief summary.


The defer function is a special function that is executed at the end of a function’s execution, for instance after the return statement or by panicking. It should be added immediately after the resource has been acquired (example).


When the Go runtime detects unexpected failures (e.g. out-of-bounds array access) it panics.

  • During a panic, normal execution stops, all deferred function calls in the goroutine are executed, and the program crashes with a log message.
  • A panic’s log message includes the panic value (i.e. error message) and, for each goroutine, a stack trace.


If the built-in recover function is called with a deferred function and the function containing the defer is panicking, recover ends the current state of panic and returns a panic value.


Go supports two styles of concurrent programming:

  • Communicating sequential processes (CSP) where values are passed between independent activities (goroutines)
  • Shared memory multithreading where threads are being used.


Goroutines are lightweight threads. And you can have millions rather than thousands.

  • Example
  • When a program starts, its only goroutine is the one that calls the main function.
  • Once the main function returns all goroutines are terminated.
  • There is no programmatic way for one goroutine to stop another other than returning from the main function or exiting the program. But there are ways to communicate with a goroutine to request that it stops itself (using channels).

Goroutines vs threads

  • OS threads have fixed-size (~2MB) blocks of memory for their stack. In contrast, a goroutine starts with a small stack (~2KB) and grows and shrinks as needed.
  • OS threads are scheduled by the OS kernel. Instead, the Go runtime contains its own scheduler that multiplexes m goroutines on n OS threads.
  • Goroutines have no identity and hence there’s no concept of a thread local.


If the goroutines are the activities of a concurrent Go program, channels are the connections between them.

  • Channels are communication mechanisms that let one goroutine send values to another. A channel is a reference to the data structure created by make.
  • A channel has two operations; send and receive, as shown below:
ch <- x // send statement
x = <-ch // receive in an assignment statement
<-ch // receive statement where the result is discarded
  • Not passing the channel’s capacity creates an unbuffered channel, also called a synchronous channel; communication over it causes the sending and receiving goroutines to synchronize.
  • If the sender knows that no further values will ever be sent on a channel it is useful to communicate this to the receiver goroutines so that they can stop waiting. This is accomplished by closing the channel using the close function.


If you don’t need channels for communication and you just want to make sure only one goroutine can access a variable at a time to avoid conflicts, mutual exclusion can be used through sync.Mutex.


Lazy initialization in a concurrent program can be safely achieved through sync.Once. A Once consists of a mutex and a boolean variable that records whether initialization has taken place.

Project Layout

Although there is no official layout for Go projects, many popular projects follow the one provided by golang-standards. You don’t necessarily need all of those modules though. You can start with cmd and pkg, this may be enough for simple projects.


There are many IDEs and editor plugins with Go support. I personally use GoLand which I find more powerful and complete, but other options include the Visual Studio Code Go extension, IntelliJ IDEA’s Go plugin or even the vim-go plugin.


In this blogpost we presented a brief overview of the Go language. We talked about features and limitations of the language, the program structure and data types it supports, its unique approach to error handling, and went through concurrency concepts such as goroutines and channels. Finally, we covered potential project layouts and IDEs which could be used for development.

Useful Resources

Software Engineer @ExpediaGroup

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store