Go is a high-level language with low-level language features. It was designed by Robert Griesemer, Rob Pike, and Ken Thompson and released by Google in 2012. Go supports high-level language constructs and characteristics, such as sync/atomic
package to enforce thread-safe synchronization. It is statically typed, fully compiled, and hence offers better runtime efficiency (compared to Java, Python, JavaScript, and other interpretable languages).
Go is an open-source programming language with a growing market demand. It’s a good choice for fast, efficient, and reliable software development with a quick learning curve.
The Go language has a special data structure called a slice that provides a powerful and efficient way to represent arrays due to its dynamic size and flexibility.
A slice is an array with a variable length that can store the same type of elements. Internally, a slice is a reference to an underlying array. A slice always starts with 0
index, and the last element of a slice is at N-1
index where N
is the length of the slice. The size C
of the underlying array is known as the capacity of the slice where C
is always greater than or equal to N
.
Go can be viewed as a C-family language and provides the support for pointers and arrays. However, for safety reasons, there are restrictions on Go pointers (when compared with C pointers). In addition, Go supports slices, which are flexible and dynamic.
The following diagram shows a pictorial comparison of an array, a pointer, and a slice. The memory layout of a slice in Go is a structure that consists of a pointer to an array and two variables (len
and cap
indicating the length and the capacity of a slice, respectively). A pointer holds the memory address of a value. The type *T
is a pointer to the value T
. While the length of an array is fixed, the slice and the pointer may point to any element of the array (memory address). While the pointer does not have a particular size or capacity associated, the memory layout of a slice in Go contains variables to hold the length and the capacity of the slice.
Array size must be defined while defining an array variable or else [...]
must be used in array declaration to let the Go compiler determine it automatically. If nothing is put in the square brackets, then a slice is created. Valid indices for both the arrays and slices are from 0
and l-1
where l
is the length of an array or a slice. Moreover, the size of an array can't be changed after creation, however a slice in this context is flexible.
Another difference arises while dealing with function arguments. All function arguments are passed by value in Go language. Therefore, while passing an array to a function, a copy of that array is created and passed to the function. Consequently, changes to the array done inside the function are lost on function's return. On the contrary, as mentioned above, a slice is a header containing pointer to an underlying array. Therefore, while passing a slice to a function, copy of that header is made and passed to the function. Hence, argument passing using a slice is faster and also preserves any changes performed in the function on the original slice.
Therefore, given this flexibility and power of a slice, a slice is usually preferable to an array while programming in Go. However, arrays are still useful in Go when a detailed memory layout is needed. For example, while reading a .wav
(audio) file, there's an informational header with a fixed structure. Many fields in this header are usually of no concern in the implementation. While committing a write/read in this type of file in Go, a structure containing the desired fields is defined. In between, arrays of an appropriate size are defined to take care of unimportant fields in the audio file header.
A pointer is represented by *T
where T
is the type of the stored value. For example in ptr *int
, the ptr
is a pointer to an int
. The *
operator&
operator provides the address of the variable (which is also a pointer to the variable).
While function arguments are passed by value in Go, pointers are one of the ways to avoid copy overhead for passing structures and arrays. Additionally, pointers to structures are handy while creating data structures like linked lists and binary trees. For further clarity about internal layout of a pointer in Go language, please refer to the figure and the discussion in this Go language lesson. However, Go doesn't support the pointer arithmetics that are available in C. Compared with pointers, slices are passed to a function without the need to use a pointer since Go passes the pointer to the underlying array of a slice internally.
Each slice is based on an underlying array with a length that's the same as the capacity of the slice. If an existing array is connected with a slice, any changes to the slice affect the referenced array. However, when the capacity of the slice changes, contents of the old underlying array are copied to a new array with the updated capacity, and the slice is pointed to the new array. For a better understanding, please refer to the coding example in this Go language lesson.
A slice can be created in the following different ways:
With an empty slice using var
keyword as follows: var aSlice []datatype
.
With an array syntax without specifying the array size to declare a slice. For example, aSliceArray := []datatype {value1, value2, ..., valueN}
.
By slicing an existing array to define a new slice. For example, anArray := [length]datatype {value1, value2, ..., valueN}
aSliceArray := anArray[i1:i2]
, i1
, and i2
are the desired indices here.
With a slice using new
keyword. For example, aSliceArray := new([capacity] datatype)[0:length]
.
By declaring a slice using the make()
function. For example, aSliceArray := make([]datatype, length, capacity)
.
Note: For cases 3–5 above, values of
length
andcapacity
must be defined in the code at the time of declaration.
In the following code widget, lines 10–24 show different ways to create (declare or initialize) an int
type slice. In addition, line 31 shows that slices of other types (e.g., string
) can also be defined. The function reflect.ValueOf(var).Kind()
in lines 32 and 40 returns the name of data type kind (slice
). Functions len(var)
and cap(var)
in lines 32 and 41 return ==
operator can be used to determine whether a slice is nil
or not (see lines 33–34). However, comparing two slices using the ==
operator will result in an error. (Please run the following code widget again after uncommenting line 36.)
package mainimport ("fmt""reflect")func main() {//Creating and using an empty slicevar intSlice []intprintSlice(intSlice)// A slice using arrayarray := [4]int {0, 5, 10, 15}aSlice1 := array[0:4]// A slice using array syntax without specifying array sizeaSlice2 := []int {1, 6, 11, 16}// A slice using new keywordaSlice3 := new([10]int)[0:4]// A slice using the make() functionaSlice4 := make([]int, 4, 10)printSlice(aSlice1)printSlice(aSlice2)printSlice(aSlice3)printSlice(aSlice4)var aSlice5 = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}fmt.Printf("%s %s len=%v cap:%v\n", "string-type", reflect.ValueOf(aSlice5).Kind(), len(aSlice5), cap(aSlice5))fmt.Println(intSlice == nil)fmt.Println(aSlice1 == nil)//Uncomment following to see error: Comparing two slices using ==//fmt.Println(aSlice1 == intSlice)}func printSlice(s []int) {fmt.Print(reflect.ValueOf(s).Kind())fmt.Printf(" len=%d cap=%d %v\n", len(s), cap(s), s)}
After slice creation, different slice operations can be used to add, access, and change the slice elements. Following code widget shows the syntax for access and modification operations (lines 12–13) on an int
slice. Lines 17–19 demonstrate the addition of new elements in a slice using the append()
method. However, there's no built-in method in Go language for item removal from a slice for optimization reasons. This task is achieved by defining a function remove_element(intArray []int, index int) []int
in lines 25–27 that intelligently uses a built-in append()
method.
package mainimport ("fmt"// "reflect")func main() {//Add, access, change, and append slice elementsaSliceArray := make([]int, 2, 4)aSliceArray[0] = 1aSliceArray[1] = 2fmt.Println("Slice contents:", aSliceArray)fmt.Printf("len=%d cap=%d\n", len(aSliceArray), cap(aSliceArray))aSliceArray = append(aSliceArray, 3, 4, 5, 6, 7)fmt.Println("Slice after append():", aSliceArray)fmt.Printf("len=%d cap=%d\n", len(aSliceArray), cap(aSliceArray))aSliceArray = remove_element(aSliceArray, 3)fmt.Printf("Slice after removal: %d \nlen=%d cap=%d\n", aSliceArray, len(aSliceArray), cap(aSliceArray))}func remove_element(intArray []int, index int) []int {return append(intArray[:index], intArray[index+1:]...)}
In brief, Go is an ideal programming language to learn with so many attractive features. If you are interested in exploring further resources, please refer to the following courses and don't forget to share your feedback and opinions with us!
Grokking Coding Interview Patterns in Go
With thousands of potential questions to account for, preparing for the coding interview can feel like an impossible challenge. Yet with a strategic approach, coding interview prep doesn’t have to take more than a few weeks. Stop drilling endless sets of practice problems, and prepare more efficiently by learning coding interview patterns. This course teaches you the underlying patterns behind common coding interview questions. By learning these essential patterns, you will be able to unpack and answer any problem the right way — just by assessing the problem statement. This approach was created by FAANG hiring managers to help you prepare for the typical rounds of interviews at major tech companies like Apple, Google, Meta, Microsoft, and Amazon. Before long, you will have the skills you need to unlock even the most challenging questions, grok the coding interview, and level up your career with confidence. This course is also available in JavaScript, Python, Go, and C++ — with more coming soon!
Go Brain Teasers
Who knew puzzles could be fun? Puzzles and brain teasers are a great way to practice your critical thinking and improve your logic and problem-solving skills. This course provides a basic understanding of Go. The teasers in this course will help you practice what you already know and help avoid future mistakes.
The Way to Go
Go (sometimes called Golang) is one of the most popular languages today, and is a key part of many enterprise tech stacks. Many developers prefer Go to other languages like C++ and Scala because of its memory management model which allows for easier concurrency. In this course, you will learn the core constructs and techniques of the language. After going through the basics, you will then learn more advanced Go concepts like error-handling, networking, and templating. You'll learn how to program efficiently in Go by gaining knowledge of common pitfalls and patterns, as well as by building your own applications.
Building a Backend Application with Go
In modern backend applications, many programming languages can be chosen, including Go—a programming language that provides high performance with concise syntax and can also be used to develop backend applications, including REST API and GraphQL applications. This course will cover many tools that can be used to develop a backend application, including fiber for REST API application, gqlgen for GraphQL application, and apitest for testing purposes. In this course, you’ll learn about the main concepts of web applications, including HTTP, REST API, databases, and GraphQL. You’ll cover how to develop a backend application and deploy the application as well. By the end of the course, you’ll be able to develop a backend application using REST API and GraphQL approach. Also, you’ll be able to test and deploy the backend application that has been developed.
Free Resources