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:
- Overview of Golang features
- Basic terms and concepts of Go
- Intermediate concepts of Go
- Advanced concepts of Go
- Resources
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"
fmt.Println(f)
}
Operators
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
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"
fmt.Printf(s)
}
Output: 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)
}
}
Output:
The character H is in position 0
The character e is in position 1
The character l is in position 2
The character l is in position 3
The character o is in position 4
The character , is in position 5
The character is in position 6
The character W is in position 7
The character o is in position 8
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)
fmt.Printf(mystring)
}
Output: Hello
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.
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:
...
default:
...
}
Like the if
construct, a switch
can also contain an initialization statement.
switch initialization; {
case val1:
...
case val2:
...
default:
...
}
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 {
fmt.Println(value)
}
-
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
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.
pack1.Function(arg1,arg2,...,argn)
Here function
is a function in pack1
, and arg1
is 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. To see more, take a look at the EdPresso shot on the Golang print function.
package main
import "fmt"
func main() {
var mystring = "Hello world"
fmt.Printf("The string is %s", mystring)
}
Output: The string is Hello World
Maps
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"])
}
Output:
Map literal at "one" is: 1
Map created at "key2" is: 3.141590
Map assigned at "two" is: 3
Map literal at "ten" is: 0
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 (
"fmt"
"reflect"
)
func main() {
var intSlice []int
var strSlice []string
fmt.Println(reflect.ValueOf(intSlice).Kind())
fmt.Println(reflect.ValueOf(strSlice).Kind())
}
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.
printSlice(slice)
// Append works on nil slices.
slice = append(slice, 0)
printSlice(slice)
// Slices can be appended to as many times.
slice = append(slice, 1)
printSlice(slice)
// We can add more than one element at a time.
slice = append(slice, 2, 3, 4)
printSlice(slice)
}
Output:
length=0 capacity=0 []
length=1 capacity=1 [0]
length=2 capacity=2 [0 1]
length=5 capacity=6 [0 1 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!
fmt.Println(err)
} else {
// No errors!
fmt.Println(answer)
}
}
Output: Cannot divide by 0!
Goroutine
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.
If you want a more robust introduction to Goroutines, check out the article Anatomy of goroutines in Go.
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 (
"fmt"
"time"
)
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()")
}
Output:
In main()
About to sleep in main()
Beginning longWait()
Beginning shortWait()
End of shortWait()
End of longWait()
At the end of main()
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. See the documentation here.
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. Check that out here.
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 (
"fmt"
"time"
"github.com/inancgumus/myhttp"
)
func main() {
mh := myhttp.New(time.Second)
response, _ := mh.Get("https://jsonip.com/")
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
Resources
Golang is an exciting language that speeds up development and accommodates your real-world needs. Luckily, there are dozens of useful resources to learn, practice, and share Go with the world. Take a look below to get started.
Courses
- The Way to Go: the definitive place to learn the core constructs and techniques of Go with hands-on practice
- Introduction to Programming in Go: detailed instruction for beginners
- Mastering Concurrency in Go: for intermediate Go learners looking to upskill
Documentation and Guides
- Tour of Go: official Go tutorial
- Official Golang Documentation: dive deeply into the code
- GitHub Go Bootcamp: learn by practicing, for beginners
Top comments (2)
I learned a lot from this post, thanks a lot🙂 Go is an awesome language!
The code snippet has lots of room for improvement, some code are not clear and some are in wrong indentation.