Go Fundamentals: A Practical Approach to Data Types and Structures


Go, commonly referred to as Golang, is a statically typed programming language designed to simplify the creation of reliable and efficient software. This series on Golang aims to demystify the language, enabling readers to get started and develop high-quality software quickly. This first article will take a practical approach to understanding some of its fundamental concepts.
Prerequisites
Before you begin this tutorial you'll need the following:
Computer
Prior programming knowledge
Go 1.x installed on your machine
A Go Playground or Code Editor (Vscode)
Program Template
package main
import ("fmt")
func main() {
fmt.Println("Hello, World!")
}
The above program is a typical Go code that you’ll get used to seeing in every Go file you work with. The output will be “Hello, World!”
package is a reserved keyword in Golang, it helps to keep related code together in one place. In the code above, package main means all the rest of the code in this file belongs to the ‘main package’. When a Go program is run, it looks for the function named main and runs that first and that’s why we named this function main.
import (“fmt“) says we’ll be using text-formatting code from the “fmt“ package**.** Go files or programs always have one or more import statement. Each program or file needs to import other packages before its code can use the code other packages contain. In addition, to load all the Go code on our computer would cause our program to be very slow, so we can instead import only the packages we need.
func main() {} is a special function named main, it gets run first when we run our program. In Go files or programs, there can only be one main function in the entire codebase, think of this as a room that has an entrance via the door; that single door is likened to this main function we’re talking about here. Also, main is a reserved keyword in Go.
fmt.Printlin(“Hello, World!“) is calling the “Printlin“ function from the “fmt“ package to display “Hello, World!“ in our terminal or console. “Println“ simply means “Printing and break to the next line“, it can take one or more arguments separated by commas. For instance,
fmt.Println ("Hello,", "World!")
Data Types and Variables
The Go basics types are String, Runes, Booleans, and Numbers (int,float64, float32, complex64, complex128).
String
A string is a series of bytes that usually represent text characters. We can define strings directly within our code using string literals, that is text between double quotation marks that Go will treat as string. Below are the examples:
"Hello, Gophers!" // The output is Hello, Gophers!
"Hello, \nGophers!"
/* Because of the escape sequence, the output will be:
Hello,
Gophers!
*/
"Hello, \tGophers!" // The output is Hello, Gophers!
"Code: \"\"" // The output is Code: ""
Escape Sequence | Value |
\n | A newline character |
\t | A tab character |
\” | Double quotation marks |
\ | A backlash |
Runes
In Go, runes are used to represent single characters. unlike String literals which are written surrounded by double quotation marks (““), rune literals are written with single quotation marks (‘‘). Go uses the Unicode standard for storing runes and they are kept as numeric codes, not the characters themselves. Let’s take a look at the examples below:
fmt.Println('A') // The output is 65
fmt.Println('B') // The output is 66
fmt.Println('\t') // The output is 9
fmt.Println('\n') // The output is 10
fmt.Println('\\') // The output is 92
fmt.Println('*') // The output is 1174
Boolean
In Golang, the Boolean type is written as bool. Boolean values can either be true or false. They are useful with conditional statements, which cause a section of code to run only if a condition is true or false. Let’s take a look at the examples below:
3 == 3 // true
5 > 6 // false
10 < 20 // true
3 == 3 && 5 > 6 // false
3 == 3 || 5 > 6 // true
5 > 6 && 10 < 20 // false
5 > 6 || 10 < 20 // true
Numbers
In Golang, a number type can be int
(int8 int16 int32 int64), unit
(uint8 uint16 uint32 uint64 uintptr), float32
, float64
, complex64
, complex128
. The int
, uint
, and uintptr
types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. When you need an integer value you should use int
unless you have a specific reason to use a sized or unsigned integer type. Likewise, when you need a decimal value, you should use float64
. Numbers are used for mathematical operations using arithmetic operators.
Variables
Just like in any programming language, a variable is a piece of storage containing a value. A variable can be named by using a variable declaration. To declare a variable in Go, var
keyword followed by the desired name and the type of value the variable will hold. For example,
var age int // This variable can only hold an integer value
var height, examScore float64 // It's also possible to delcare multiple variables of the same type at once
var userName string // This variable can only hold a string value
Variable declaration and assignment
Now that we’ve declared our variables, the next thing is to assign values to them. You can assign any value of that type to a particular variable with =
. For instance, let’s assign values to the above variables that we declared.
age = 30
height, examScore = 167.64, 68.6 // Assigning multiple variables at once
userName = "John Doe"
Alternatively, variable declaration and assignment can be done using the shorthand method, in Go if you know what the initial value of a variable is going to be as soon as you declare it, then you can use a short variable declaration. Instead of explicitly declaring the type of the variable and later assigning to it with =
, you can do both at once using :=
. Now let’s redeclare and assign the above variables to values using short variable declaration.
age := 30 // same as var age int = 30
height, examScore := 167.64, 68.6 // same as var height, examScore float64 = 167.64, 68.6
userName := "John Doe" // same as var userName string = "John Doe"
When declaring a variable without specifying an explicit type (either by using the :=
syntax or var =
expression syntax), the variable's type is inferred from the value on the right-hand side and this concept is called Type Inference.
Zero Values
A variable declared without assigning it a value, that variable will contain the zero value. For instance,
var myInt int // The zero value for int type is 0
var myFloat float64 // The zero value for float64 type is 0
var myString string // The zero value for string type is false
var myBool bool // The zero value for bool type is false
Constants
Constants are declared like variables but with the const
keyword. It can be character, string, boolean, or numeric values. The caveat about constant variables is that they cannot be declared using the :=
syntax.
Let’s write a simple program to print the above user information to the terminal.
package main
import (
"fmt"
)
const totalMark = 100 // constant variable
// it is important to note that variables declare here are package scope and can be accessed anywhere in this code
func main() {
// variables declared here are function scope and can only be accessed inside this function
age := 30
height, examScore := 167.64, 68.6
userName := "John Doe"
fmt.Println(userName)
fmt.Println("whose height is", height, "at the age of", age)
fmt.Println("scored",examScore, "/",totalMark)
}
Output:
John Doe
whose height is 167.64 at the age of 30
scored 68.6 / 100
Identifiers
Go has a simple set of rules that apply to the names of variables, functions, and types:
A name must begin with a letter and can have any number of additional letters and numbers
If the name of a variable, function, or type begins with a capital letter, it is considered exported and can be accessed from packages outside the current one. This is why
P
infmt.Println
is capitalized so it can be used from the main package or any other. Similarly, if a variable function or type name begins with a lowercase letter, it is considered unexported and can only be accessed within the current package.
The Go community follows some additional conventions as well:
If a name consists of multiple words, each word after the first should be capitalized, and they should be attached without spaces between them, like this: examScore, UserName, and so on. The first letter of the name should only be capitalized if you want to export it from the package. This style is often called a camel case.
When the meaning of a name is obvious from the context, the Go community’s convention is to abbreviate it: to use
i
instead ofindex
,max
instead ofmaximum
, and so on.
Data Structure
The basic data structure in Go is as follows:
Array
Slices
Map
Struct
Array
An array is a collection of values that all share the same type (an array of strings, an array of booleans). The values an array holds are called its elements. In Go, once an array is declared, elements can neither be added nor removed from the array. Elements in an array are numbered, starting with 0, an element number is called its index.
var names [lenght of the array] type // General syntax
var names [7] string // an array to hold 7 values of type string
names[0] = "John" // set the value of first element to John
names[1] = "Doe" // set the value of second element to Doe
fmt.Println(names) // the output will be ["John","Doe"]
fmt.Println(len(names)) // this is to print the lenght of the array and the output will be 2
Using array literals to do short variable declarations with :=
countries := [7] string {"Nigeria", "Portugal", "Austria", "Switzerland", "Poland", "Norway", "Spain"}
fmt.Println(countries[1], countries[4], countries[2]) // the output will be Portugal Poland Austria
scores := [5] int {55,54,66,70,82}
fmt.Println(scores[0], scores[1], scores[3]) // the output will be 55 54 70
it is important to note that the zero value for an array is 0, that is when an array is declared without values, Go sets its value to 0.
Slices
Like arrays, slices are made up of multiple elements of the same type. Unlike arrays, functions are available that allow us to add an extra element to the end of a slice. To declare the type for a variable that holds a slice, you use an empty pair of square brackets, followed by the type of elements the slice will hold. Also, unlike with array variables, declaring a slice variable doesn’t automatically create a slice. Ipso facto, you can call the built-in make
function; you pass make
the type of the slice you want to create (which should be the same as the type of the variable you’re going to assign it to), and the length of the slice it should create.
var books []string // declare a slice variable
books = make([]string,4) //create a slice with seven strings
//short variable declaration for slice
books := make([]string,4)
books[0] = "The Outlier" // assign a value to the first element
books[1] = "The Atomic Habits" // assign a value to the second element
books[2] = "Game Theory" // assign a value to the third element
books[3] = "The MBA Guide" // assign a value to the fourth element
fmt.Println(books[2]) // the output will be Game Theory
// getting the length of a slice variable
fmt.Println(len(books)) // the output will be 7
// when you know in advance what values a slice will start with, there's no need to call the make function
subjects := []string {"Games", "Chemistry", "Physics"}
// Add onto a slice with the "append" function
subjects = append(subjects, "Mathematics", "Science")
fmt.Println(subjects) // the output will be [Games Chemistry Physics Mathematics Science]
As with arrays, if you access a slice element that no value has been assigned to, you’ll get the zero value for that type back. Unlike arrays, the slice. the variable itself also has a zero value which is nil
. That is, the slice variable that no slice has been assigned to will have a value of nil
.
floatSlice := make([]float64, 5)
boolSlice := make([]bool, 5)
fmt.Println(floatSlice) // the output will be 0
fmt.Println(boolSlice) // the output will be false
var intSlice []int
fmt.Println(intSlice) // the output will be []int(nil)
var stringSlice []string // the output will be []string(nil)
Map
In Go, a map
is a collection where each value is accessed via a key. To declare a variable that holds a map
, you type the map
keyword, followed by square brackets ([]) containing the key type; just as with slices, declaring a map
variable doesn’t automatically create a map
; you need to call make
the function (the same function you can use to create slices). Instead of a slice type, you can pass make
the type of the map
you want to create (which should be the same as the type of the variable you’re going to assign to it).
var contacts map[key type] value type // general syntax
var contacts map[string]int //Declare a map variable
contacts = make(map[string]int) // actually create the map
//using the short variable declaratioin
contacts := make(map[string]int) // create a map and declare a variable to hold it
//Multiple map literal
contacts := make(map[string]int) {
"John": 823,
"Jane": 553,
"Joe": 678,
"Kenny": 990
// it is important to add a comma to the last key, else Go will throw error
}
fmt.Println(ranks["John"]) // the output will be 823
fmt.Println(ranks["Jane"]) // the output will be 553
fmt.Println(contacts["Joe"] // the output will be 678
fmt.Printlin(contacts["Kenny"]) // the output will be 990
Unlike Array and Slices which only let you use integer indexes, You can choose almost any type to use for a map’s keys. Also, just like slices, the zero value for the map variable is nil
. If you declare a map variable but don’t assign it a value, its value will be nil
. This means no map exists to add new keys and values to. If you try, you’ll get a panic:
// Wrong approach
var nilMap map[int]string
fmt.Printf("%#v\n",nilMap)
nilMap[2] = "two"
/* The output will be:
map[int]string(nil)
panic: assignment to entry in nil map
*/
// Correct approach
var myMap map[int]string = make(map[int]string) //create a map first
myMap[2] = "two" // and then add values to it
fmt.Printf("%#v\n",myMap) // the output will be map[int]string{2: "two"}
Struct
A struct
(short for “structure“) is a value that is constructed out of other values of many different types. Whereas a slice
might only be able to hold string
values or a map
might only be able to hold int
values, you can create a struct
that holds string
values, int
values, float64
values, bool
values, and more all in one convenient group. A struct
is declared using the struct
keyword, followed by curly braces. Within the braces, you can define one or more fields
which are values that the struct
groups together. Each field definition appears on a separate line and consists of a field name, followed by the type of value that field will hold.
// general syntax
struct {
field1 string
field2 int
field3 float64
field4 bool
}
// a struct with name, age, gender, and isMarried
var userInfo struct {
name string
age int
gender string
isMarried bool
}
// Access struct fields using the dot operator
userInfo.name = "John Doe"
userInfo.age = 18
gender = "male"
isMarried = true
fmt.Println(userInfo.name) // the output will be John Doe
fmt.Println(userInfo.age) // the output will be 18
fmt.Println(userInfo.gender) // the output will be male
fmt.Println(userInfo.isMarried) // the output will be true
Defined types and structs
Type definitions let you create your types. They allow you to make a new defined type based on an existing type.
package main
import (
"fmt"
)
type item struct {
task string
isDone bool
}
type user struct {
name string
networth float64
}
func main() {
var todo item
todo.task = "Go Shopping"
todo.isDone = true
fmt.Println("Todo:", todo.task) // the output will be Todo: Go Shopping
fmt.Println("Status:", todo.status) // the output will be Status: tru
var userInfo user
userInfo.name = "John Doe"
userInfo.networth = 100.64
fmt.Println("User Name:", userInfo.name) // the output will be User Name: John Doe
fmt.Println("User Networth:", userInfo.networth) // the output will be User Networth: 100.64
}
Conclusion
This article introduces Go (Golang) and focuses on its basic concepts like data types, variables, constants, identifiers, and simple data structures. We cover the essential prerequisites, a basic program template, and detailed explanations of Go's data types, including strings, runes, booleans, and numbers. The article also explains variable declaration and assignment, highlighting type inference and zero values in Go. Additionally, it explores Go's main data structures such as arrays, slices, maps, and structs, with code examples to show how they work. In the upcoming article, we'll explore Functions, Pointers, Loops, Methods, and Interfaces in greater depth.
Subscribe to my newsletter
Read articles from John O. Emmanuel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

John O. Emmanuel
John O. Emmanuel
A Versatile Senior Software Engineer with over 5 years of expertise in full-stack development, focusing on React, React-Native, Node.js, Golang, and TypeScript ecosystems. Demonstrated success in spearheading remote teams and architecting high-performance, scalable web and mobile applications. Exceptional communicator skilled in fostering collaboration across diverse time zones and cultural backgrounds. Committed to driving innovation and operational excellence in distributed work environments.