Getting started with Golang: a tutorial for beginners

Apr 29, 2020 - 17 min read
Amanda Fawcett

Golang, also known as Go, is an open-source programming language created by Google developers Robert Griesemer, Ken Thompson, and Rob Pike in 2007. It was created for ease, and many developers praise it for building simple, reliable programs.

Since its release, Golang has gained increasing popularity. In 2009 and 2016, it was pronounced the language of the year. It was given the 10th place ranking in 2018, and it continues to move its way into major organizations.

This language has a lot to offer. Anyone wanting to work at Google should know this language. That’s why today, I want to walk you through a deep-dive tutorial to the Go programming language.

Today we will discuss:

Master Golang Programming Fundamentals

Get ready for a career in web development by learning Google’s Golang language.

The Way to Go


Overview of Golang features

This general-purpose programming language includes many great features from other programming languages. It is compiled, simple, concurrent, statically-typed, and efficient. Go improves upon these aspects of programming languages and simplifies the working environment for developers.

Go is essentially an imperative language that accommodates concurrency concepts. It brings some of the great features of object-oriented programming, like interfaces, but does not include some of the pitfalls. Go was intentionally designed to exclude the more “heavy-weight” features of OOP.

In that respect, Go is hybrid, utilizing the best features of many languages with a clear, expressive type system while remaining lightweight and easy to learn.

Go can be used for all kinds of software development solutions such as a system programming language, a general programming language, or general support. It can handle heavy server-centric web services, text-processing problem, and heavy-duty distributed applications.

Why learn Golang?

Familiar and easy to learn. Go belongs to the C-family, so it shares many beloved syntactic similarities to languages like Java and C++, but Go offers a more concise syntax, so it’s easier to learn and read. Similar to Python and Ruby, it also integrates many features of dynamic programming.

Meets developer needs. Go attempts to meet some common needs that developers face. It speeds up the software development process while not compromising on efficiency. Go aims to support the developing market with network communication, memory management, and speed.

Simplicity of server-side. Go makes it easy to work with the server-side of your code. The standard Go library provides the standard HTTP protocol.

Now that we have a sense of what Go is and what it brings to the table, let’s jump into the basics. Today, we will be introducing the major concepts and core constructs of the Go programming language to get you started. As always, a more robust course is needed to teach you all the ins-and-outs.

Let’s jump in.

Basics terms and concepts of Go

Filenames, keywords, identifiers

The Go source code is stored in .go files. All filenames are lowercase, and you can use _ to separate multiple words. As with most filenames, you cannot use spaces or special characters.

Keywords in Go function similarly to most programming languages. These are reserved words that carry special meaning to use in your code. Unlike Java or C++, Go has far fewer keywords, making it easier to use and learn. These keywords are:

Identifiers are similar to keywords, but you make these as the programmer. You can assign a name to elements like variables, templates, etc. And like most programming languages, identifiers are case sensitive. They must begin with a letter or an underscore and are followed by digits. The blank identifier _ can be used in declarations or variable assignments. There are also 36 predeclared identifiers, which are:

Basic structure

Programs in Go are built up of keywords, operators, types, functions, and constants. Code is structured in statements, but it does not need to end with a ; like many other C-family languages. If multiple statements are written on one line, you must separate them with ;.

Go uses similar punctuation characters to other languages, including . , ; : and ....

Go uses three delimiters in its code: ( ) [ ] and { }.

Data types and variables

Like many programming languages, variables contain different types of data that define the set of values or operations that can act upon those values. In Go, there are four main data types you can work with:

  • Elementary (aka. primitive): int, float, bool, string
  • Structures (aka. composite): struct, slice, map, array, channel
  • Interfaces: describe the behavior of a type

In Go, a structured type has no inherent value but rather the default value nil.

A variable is a value that can be changed during execution. To declare a variable, we use the var keyword.

var identifier type = value

In this example, identifier is the name of the variable, and type is the type. Unlike other C-family languages, we write type after the variable identifier. When we declare a variable in Go, memory is initialized. We must also give a value to our variables using the = operator. This process is called assigning a variable.

There is also a shorthand for declaring variables.

   f := "fruit"


Like in many programming languages, operators are built-in symbols that perform logical or mathematical operations. There are three types of operators in Golang, arithmetic, logical, and bitwise.

Logical operators are similar to other programming languages. Go, however, is very strict about the values that can be compared. These operators include:

  • Equality operator ==
  • Not-Equal operator !=
  • Less-than operator <
  • Greater-than operator >
  • Less-than equal-to operator <=
  • Greater-than equal-to operator >=

Bitwise operators work on integer variables that have bit-patterns of equal length. Some of the bitwise operators are:

  • Bitwise AND operator &
  • Bitwise OR operator |
  • Bitwise XOR operator ^
  • Bit CLEAR operator &^
  • Bitwise COMPLEMENT operator ^

Arithmetic operators include + / % and *.

These perform common arithmetic operations, and there are even some shortcuts. For example,

b = b + a

can be shortened as

b += a


Strings implement functions to manipulate UTF-8 encoded strings. They are UTF-8 encoded by default, so they can contain characters from any language. These are defined between double quotes “ “, can include a sequence of variable-width characters, and are immutable.

Go strings are generally better than strings in other languages because they use less memory, and you don’t need to decode them due to the UTF-8 standard.

There are two kinds of string literals in Golang, interpreted and raw. Interpreted strings are surrounded by quotes, and raw strings are surrounded by backticks.

To declare a string, we use the string keyword. Look at the example below to see how it’s done.

package main
import "fmt"

func main() {
  var s string = "Hello, World"

You can loop over characters in a string to access individual elements. We use the for loop, which we will discuss more later.

package main
import "fmt"

func main() {
  var s string = "Hello, World"
  for index, character := range(s){
    fmt.Printf("The character %c is in position %d \n", character, index)

You can also use string to form a string from a slice of byte values. Look at the example to see how it’s done.

package main
import "fmt"

func main() {
  myslice := []byte{0x48, 0x65, 0x6C,  0x6C, 0x6f} 
  mystring := string(myslice) 


Times and dates

In Golang, the package time provides the ability to measure and display time. For example, we can use time.Now( ) to display the current time, and t.Day ( ) to obtain smaller parts. There are many useful features of Go’s time package, such as the function Since(t Time), which returns the time elapsed since t. You can make your own time formats as well.

t := time.Now()
fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year()) // e.g.: 29.10.2019

For more on Go’s time package, check out the documentation.

Keep the learning going.

Learn Golang without scrubbing through videos or documentation. Educative’s text-based courses are easy to skim and feature live coding environments - making learning quick and efficient.

The Way to Go

Intermediate concepts of Go

Control structures

Control structures are similar to that of C, but they are generally more simplified and flexible. There are no do or while loop; instead, Go uses flexible for and switch loops.

There are also new control structures, such as a type switch and select. We do not use parentheses, and the bodies are brace-delimited. Let’s take a deeper look at Go control structures.

if-else: this construct tests for a conditional statement, either logical or boolean. If a statement is true, the body between the { } is executed. If it is false, the statements are ignored, and the statement after the if is executed. Keep in mind that the braces are mandatory even if there is only one statement in the body.

switch-case: this structure is used instead of long if statements that compare variables to values. This statement makes it easy to transfer-flow of execution in your code.

switch is generally more flexible than other languages. It takes this general form.

switch var1 {
case val1:
case val2:

Like the if construct, a switch can also contain an initialization statement.

switch initialization; {
case val1:
case val2:

select: this statement means we can wait on multiple channel operations, which we will discuss more later.

for-range: in Go, this statement allows us to iterate over an expression that evaluates to an array, slice, map, string, or channel. The basic syntax is below.

for index, value := range mydatastructure {
  • index: the index of the value we want to access.
  • value: the value on each iteration.
  • mydatastructure: holds the data structure whose values we are accessing in the loop.

Keep in mind that this example is a generalization. To learn more about case-by-case examples, take a look at the EdPresso shot on the for-range loop here


Functions are the basic building blocks of Golang, as it shares many features of functional languages. As I mentioned before, functions are data since they have values and types. A Go program is built up of several functions. It is best to start with main( ) function and write them in calling, or logical, order.

Functions break down problems into smaller tasks and enable us to reuse code. There are three types of functions in Go. All of them end when they have executed their last statement before } or when it executes a return statement.

  • Normal functions that use an identifier
  • Anonymous or lambda functions
  • Methods

We write functions using this syntax, and we call them with this general format.

func g() { // VALID

and we call them with this general format.


Here function is a function in pack1, and arg1is the argument. When we invoke a function, it makes copies of the arguments, which are passed to the called function.

Let’s take a look at an example of a function to see Go in action. Here, we will dive into the printf( ) function in Golang. The print function allows you to print formatted data. It takes a template string that contains the text we will format and some annotation verbs that tell the fmt functions how to format.

fmt.printf("Sample template string %s",Object arg(s))

Conversion characters tell Golang how to format the data types. Some common specifiers are:

  • v – formats the value in a default format
  • d – formats decimal integers
  • g – formats the floating-point numbers
  • b – formats base 2 numbers

Say we wanted to print a string. The %s conversation character can be used in the template string to print string values. Look at the code below. There are many other cases where we can use the print function.

package main
import "fmt"

func main() {
  var mystring = "Hello world"
  fmt.Printf("The string is %s", mystring)


Maps, also called hashes or dicts in other programming languages, are a built-in data type in Go. The name explains their purpose: a map maps keys to values. Think of a map as a way to store key-value pairs.

You can use these for fast lookups, retrievals, or deletion of data based on keys. We declare a map using the following syntax

var m map[KeyType]ValueType
  • m is the name of the map variable
  • KeyType is the option data type of the keys in the map. This can also be declared at the time of initialization.
  • ValueType is the data type of the value in the key-value pairs.

The length of a map doesn’t need to be known at declaration, so it can grow dynamically. The value of an uninitialized map is nil. Let’s look at a specific example of a map in Golang to see how they are made:

package main
import "fmt"

func main() {
  var mapLit map[string]int   // making map
  var mapAssigned map[string]int
  mapLit = map[string]int{"one": 1, "two": 2}   // adding key-value pair
  mapCreated := make(map[string]float32)        // making map with make()
  mapAssigned = mapLit
  mapCreated["key1"] = 4.5      // creating key-value pair for map
  mapCreated["key2"] = 3.14159
  mapAssigned["two"] = 3        // changing value of already existing key
  fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
  fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
  fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
  fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])

Arrays and slices

Arrays in Go are similar to Python, but they aren’t very common in Go code because they are inflexible and have a fixed size. Instead, slices are far more common and provide greater power. Slices in Go build off of arrays, since it is an abstraction of Go’s array type.

To declare an array, we use the following syntax:

var identifier [len]type

An array is fixed in size since its length is part of its type. For example [5]int represents an array of five integers. A slice allows us to overcome some of the challenges of arrays and work with sequences of typed data without using additional memory.

A slice is a reference to a continuous section of an array, called the underlying array. A slice is dynamically sized and flexible. A slice is formed when we specify two indices, separated by a colon. We use the type specification [ ]T. T is the type of elements in the slice. We declare a slice using the following syntax:

letters := []string{"a", "b", "c", "d"}

To declare the type for a variable with a slice, we use [ ] with the type of elements for the slice.

package main

import (

func main() {
	var intSlice []int
	var strSlice []string


A slice, unlike an array, can change during execution. Additionally, slices come with the built-in append, which can return a slice that contains one or more new values. The syntax of the append method is:

slice = append(slice, elem1, elem2, ...)

Take a look at how it’s done.

package main
import "fmt"

// Helper function to. print slices
func printSlice(s []int) {
	fmt.Printf("length=%d capacity=%d %v\n", len(s), cap(s), s)

func main() {
	var slice []int // Create an empty slice of type int.

	// Append works on nil slices.
	slice = append(slice, 0)

	// Slices can be appended to as many times.
	slice = append(slice, 1)

	// We can add more than one element at a time.
	slice = append(slice, 2, 3, 4)

Now that we have a sense of some of the intermediate Go concepts, let’s move onto some of the important advanced things that Golang brings to the table. Keep in mind that there is a lot more to learn. Some other intermediate concepts include:

  • Recursive functions
  • Higher order functions
  • Structs and methods
  • Interfaces and reflection
  • and more

Advanced concepts of Go

Error handling

Go does not have an exception-handling mechanism. We use the built-in interface type error. It’s zero value is nil, so we know that there were no errors if it returns nil. The most common way to handle errors is to return the error type as the last return value of a function call to check for nil. Let’s take a look at some code to see how it’s done.

package main
import "fmt"
import "errors" // Import the errors package.

func divide(x int, y int) (int, error) {
	if y == 0 {
    return -1, errors.New("Cannot divide by 0!")
  return x/y, nil

func main() {
  answer, err := divide(5,0)
  if err != nil {
    // Handle the error!
  } else {
    // No errors!


Go comes with built-in support for concurrent applications. These are programs that execute different pieces of code simultaneously. The basic building blocks for structuring concurrent programs are goroutines and channels.

Unlike Java, concurrency support is baked into the language with specific types (chan), keywords (go, select) and constructs (goroutines). Go emphasizes concurrency rather than parallelism because Go programs may not be parallel by default. Only a single core or processor is used for a Go program, regardless of the goroutines running.

So, what are goroutines? They are methods or functions that run alongside other methods or functions. These are determined by how we call them. Think of these like threads, but they are much easier and more lightweight.

We use the keyword go to create a goroutine, so when we call a function or method with that prefix, a goroutine is executed.

You can use the variable GOMAXPROCS to tell the run-time how many goroutines can execute. GOMAXPROCS must be set to more than the default value 1, or else all goroutines will share the same thread. Let’s look at an example.

package main
import (

func main() {
  fmt.Println("In main()")
  go longWait()
  go shortWait()
  fmt.Println("About to sleep in main()")
  time.Sleep(10 * 1e9) // sleep works with a Duration in nanoseconds (ns) !
  fmt.Println("At the end of main()")

func longWait() {
  fmt.Println("Beginning longWait()")
  time.Sleep(5 * 1e9) // sleep for 5 seconds
  fmt.Println("End of longWait()")

func shortWait() {
  fmt.Println("Beginning shortWait()")
  time.Sleep(2 * 1e9) // sleep for 2 seconds
  fmt.Println("End of shortWait()")
Code from Educative course "Way To Go"

Here, the program indicates the part of the execution phase that the program is in. The functions main( ), shortWait( ), and longWait( ) start as independent processing units and then work concurrently.

Channels are used with goroutines to enable communication between them. These are typed message queues that transmit data. Think of it as a conduit that you can send typed values through. This way, we can avoid shared memory between goroutines. A channel can transmit one datatype, but we can make them for any type.

To declare a channel, we use the following format

var identifier chan datatype

A channel is also a reference type, so, to allocate memory, we use the make( ) function. Below, see how to declare a channel of strings and its instantiation.

var ch1 chan string
ch1 = make(chan string)

The Standard Library and Packages

The Go-distribution includes more than 250 built-in packages, and the API is the same for all systems. Each package introduced different functionalities to your Go code.

Let’s introduce some common packages to see what it has to offer.

  • os/exec: gives the possibility to run external OS commands and programs.
  • syscall: this is the low-level, external package, which provides a primitive interface to the underlying OS’s calls.
  • archive/tar and /zip – compress: contains functionality for (de)compressing files.
  • fmt: contains functionality for formatted input-output.
  • io: provides basic input-output functionality, mostly as a wrapper around os-functions.
  • bufio: wraps around io to give buffered input-output functionality.
  • path/filepath: contains routines for manipulating filename paths targeted at the OS used.
  • strconv: converts strings to basic data types.
  • unicode: special functions for Unicode characters.
  • regexp: for string pattern-searching functionalities.

There are also external third-party Go packages that can be installed with the go get tool. You need to verify that the GOPATH variable is set, otherwise, it will be downloaded into the $GOPATH/src directory.

There are more than 500 useful projects that you can introduce to your Go program. When introducing new functionality to an existing project, it’s good to incorporate a pre-existing Go library. This requires knowledge of the library’s API, which constraints the methods for calling the library. Once you know the API, call the library’s functions and get started.

Let’s look at the complete code of importing an external library.

package main
import (
func main() {
  mh := myhttp.New(time.Second)
  response, _ := mh.Get("")
  fmt.Println("HTTP status code: ", response.StatusCode)

Now we have a sense of some of the advanced concepts in Go. There is a lot more to learn, including:

  • Interfaces and Reflection
  • Error testing
  • Anonymous channel closure
  • Networking, templating, and web-applications
  • Best practices and pitfalls
  • and more

Wrapping up

Golang is an exciting language that speeds up development and accommodates your real-world needs. Luckily, Educative has many useful courses to learn and practice Go with the world.

One course to check out is The Way to Go. This course is the definitive place to learn the core constructs and techniques of Go with hands-on practice. By the end, you’ll have tackled advanced topics like error handling and networking as well as built your own Go practice project.

Happy learning!

Continue reading about web development

WRITTEN BYAmanda Fawcett

A free, bi-monthly email with a roundup of Educative's top articles and coding tips.