Free trial: Golang Developer
Complete a couple of real lessons from this course — no signup required.
Lesson 1: Packages, comments, variables & operators
Packages. Comments.
Any Go code lives inside a package. A package is a folder that contains .go files.
Let's look at the first simple example of a Go program:
package main
import "fmt"
func main() {
fmt.Println("Salem Bitlab!")
}
Salem Bitlab!To run this program we used the main and fmt packages.
package main is the main package. It must contain a func main() function, which is the program's entry point. When you run a Go application, this is exactly the function that gets called first.
package fmt is a standard package used for printing data to the console. Besides printing, it also supports formatted input and output.
As a program grows, its code becomes harder to read and understand. Comments are used to make the program's logic easier to follow.
Comments explain code and split it up logically. They don't take part in compilation and have no effect on how the program runs.
Types of comments
Single-line comments
package main
import "fmt"
func main() {
fmt.Println("I like Bitlab Academy!") // prints text to the console
}
I like Bitlab Academy!A single-line comment is used for a brief note about a specific line or part of the code.
Multi-line comments
package main
import "fmt"
/*
In this program,
using the "fmt" package,
we print data
to the console
*/
func main() {
fmt.Println("I like Bitlab Academy!")
}
I like Bitlab Academy!Multi-line comments are used for a detailed description of functions, structs, and larger logical blocks of code.
Variables and data types
Go is a statically typed programming language. The Go compiler checks data types before the program runs, which gives it high reliability, performance, and readability.
Go is such a fast language that I call it the Flash of the programming world.
— Suslikov Bitlabkhan, a figure publicly recognized for his contribution to acquiring useful skills
Before getting into data types, it's worth understanding what a variable actually is.
A variable is a piece of memory that holds a value. Every variable has a data type, which determines what operations can be performed on it.
Declaring a variable looks like this:
package main
import "fmt"
func main() {
var age int = 22
fmt.Println(age)
}
22To declare a variable, we use the var keyword, then the variable name and its data type. After that we can freely use the variable in the program.
Go also has a short form for declaring a variable:
package main
import "fmt"
func main() {
age := 22
fmt.Println(age)
}
22Using the := operator, Go automatically infers the variable's type from the value being assigned.
Main data types in Go
For working with numbers:
int— positive and negative whole numbersuint— positive whole numbers onlyfloat32— decimal numbers (32-bit)float64— decimal numbers (64-bit)complex64— complex numbers (2 × float32)complex128— complex numbers (2 × float64)
For working with strings:
string— text data
For boolean logic:
bool— true or false
For working with collections of data:
array— a fixed-length arrayslice— a dynamic-length arraymap— a key-value structurestruct— a group of different data typesinterface— an abstract data type
Additional data types:
pointer— a pointerfunction— a function as a data typechannel— communication between goroutines
Math operators
Math operators let you perform operations on numbers.
Addition (+)
a := 2
b := 2
c := a + b // 4
Subtraction (-)
a := 10
b := 5
c := a - b // 5
Multiplication (*)
a := 3
b := 3
c := a * b // 9
Division (/)
a := 6
b := 2
c := a / b // 3
Remainder (%)
a := 7
b := 3
c := a % b // 1
Increment (++)
a := 7
a++ // 8
Decrement (--)
a := 7
a-- // 6
That's enough material for now — it's recommended to practice by solving a few exercises. Good luck!
Sum of squares
SumOfSquares(a int, b int) int that returns a*a + b*b.SumOfSquares(2, 2) → 8SumOfSquares(3, 4) → 25-
Input:
SumOfSquares(2, 2) -
Input:
SumOfSquares(3, 4) -
Input:
SumOfSquares(1, 1) -
Input:
SumOfSquares(0, 0) -
Input:
SumOfSquares(-2, -2)
Last digit
LastDigit(n int) int that returns its last digit.LastDigit(123) → 3LastDigit(0) → 0-
Input:
LastDigit(123) -
Input:
LastDigit(0) -
Input:
LastDigit(9) -
Input:
LastDigit(100) -
Input:
LastDigit(4567)
Tens digit
TensDigit(n int) int that finds the number of tens (the second digit from the right).TensDigit(2010) → 1TensDigit(0) → 0-
Input:
TensDigit(2010) -
Input:
TensDigit(0) -
Input:
TensDigit(5) -
Input:
TensDigit(99) -
Input:
TensDigit(12345)
Country and city
Describe(country string, city string) string that returns the country and city formatted as shown in the examples.Describe("Kazakhstan", "Astana") → Country: Kazakhstan
City: AstanaDescribe("France", "Paris") → Country: France
City: Paris-
Input:
Describe("Kazakhstan", "Astana") -
Input:
Describe("France", "Paris") -
Input:
Describe("Japan", "Tokyo") -
Input:
Describe("USA", "New York") -
Input:
Describe("Germany", "Berlin")
Evaluate a formula
Evaluate(x int) int that finds the value of y = 4(x - 3) - 7(x - 3) + 2 for the given x.Evaluate(4) → -1Evaluate(-4) → 23-
Input:
Evaluate(4) -
Input:
Evaluate(-4) -
Input:
Evaluate(3) -
Input:
Evaluate(0) -
Input:
Evaluate(10)
Lesson 2: Conditionals & Logical Operators
What are conditions in Go?
In real programs, code doesn't always run top to bottom in the same way. Sometimes the program has to make a decision: run one block of code, or another.
For example:
- if the user's age is 18 or older — allow access;
- if the password is correct — log in;
- if the balance is enough — process the payment;
- if the score is high — print a success message.
Go uses conditional statements for situations like these. They let the program choose which code to run.
A condition is an expression whose result is always true or false.
How does a condition work?
A conditional statement checks an expression. If the result is true, one block of code runs. If the result is false, that block is skipped, or a different block runs instead.
The if statement
The simplest conditional statement is if. It only runs its code when the condition is true.
package main
import "fmt"
func main() {
age := 20
if age >= 18 {
fmt.Println("Access granted")
}
}
Доступ разрешёнHere the program checks the condition age >= 18. The variable age equals 20, so the condition is true, which means the code inside the if block runs.
Important: in Go, the condition inside if isn't wrapped in parentheses. But the curly braces { } are required.
The if ... else statement
When you need to run one piece of code if a condition is true, and a different piece if it's false, you use if ... else.
package main
import "fmt"
func main() {
age := 15
if age >= 18 {
fmt.Println("Access granted")
} else {
fmt.Println("Access denied")
}
}
Доступ запрещёнage equals 15. The condition age >= 18 returns false. So the if block is skipped, and the else block runs instead.
Multiple conditions: else if
Sometimes two options aren't enough. For example, you might need to check a student's score and print a different result for each range. That's what else if is for.
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("Excellent")
} else if score >= 75 {
fmt.Println("Good")
} else if score >= 50 {
fmt.Println("Could be better")
} else {
fmt.Println("Failed")
}
}
ХорошоThe program checks the conditions top to bottom:
score >= 90 — false
score >= 75 — true
"Хорошо" is printed
the remaining conditions are never checked
Comparison operators
Comparison operators are commonly used to build conditions. They compare values and return either true or false.
==— equal to!=— not equal to>— greater than<— less than>=— greater than or equal to<=— less than or equal to
package main
import "fmt"
func main() {
a := 10
b := 5
fmt.Println(a == b)
fmt.Println(a != b)
fmt.Println(a > b)
fmt.Println(a < b)
}
false
true
true
falseLogical operators
Logical operators let you combine several conditions. That's useful when a single comparison isn't enough.
&&— AND, both conditions must be true||— OR, at least one condition must be true!— NOT, flips true to false and false to true
When you need to check several rules at once, && and || are the ones you'll reach for most often.
Example with &&
The && operator means both conditions must be true. For example, a user can only log in if both the login and password are correct.
package main
import "fmt"
func main() {
login := "admin"
password := "12345"
if login == "admin" && password == "12345" {
fmt.Println("Welcome")
} else {
fmt.Println("Invalid login or password")
}
}
Добро пожаловатьExample with ||
The || operator means it's enough for just one condition to be true. For example, a city might be called Astana or Nur-Sultan.
package main
import "fmt"
func main() {
city := "Astana"
if city == "Astana" || city == "Nur-Sultan" {
fmt.Println("Hello, capital")
} else {
fmt.Println("That's a different city")
}
}
Здравствуй столицаExample with !
The ! operator flips a boolean expression to its opposite. If it was true, it becomes false. If it was false, it becomes true.
package main
import "fmt"
func main() {
isBlocked := false
if !isBlocked {
fmt.Println("User is active")
} else {
fmt.Println("User is blocked")
}
}
Пользователь активенInitializing a variable inside if
Go lets you create a variable right inside an if statement. That variable is only available within that condition.
package main
import "fmt"
func main() {
if age := 19; age >= 18 {
fmt.Println("Adult")
} else {
fmt.Println("Minor")
}
}
СовершеннолетнийThe age variable is created right before the condition is checked. Once the if ... else statement ends, that variable can no longer be used.
The switch ... case statement
When you need to compare one value against several options, switch ... case is a clean way to do it.
It makes the code cleaner when there are a lot of options.
package main
import "fmt"
func main() {
day := 3
switch day {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
case 3:
fmt.Println("Wednesday")
case 4:
fmt.Println("Thursday")
case 5:
fmt.Println("Friday")
default:
fmt.Println("Weekend or unknown day")
}
}
СредаIn Go, you don't need to write break after each case. Once a matching case runs, the switch statement ends automatically.
default runs when none of the cases matched.
switch without a value
Go also lets you use switch without a specific value. In that case, each case becomes its own condition.
package main
import "fmt"
func main() {
temperature := 30
switch {
case temperature >= 30:
fmt.Println("Hot")
case temperature >= 20:
fmt.Println("Warm")
case temperature >= 10:
fmt.Println("Cool")
default:
fmt.Println("Cold")
}
}
ЖаркоPractice examples
To really get comfortable with conditions, it helps to solve a few simple exercises.
- Check whether a number is positive or negative.
- Check whether a number is even or odd.
- Check whether a user can get access based on their age.
- Check a user's login and password.
- Print the name of the day of the week for a given number.
Summary
Conditional statements let a program make decisions. Without conditions you can't write a real program, because almost all logic depends on different situations.
ifis used for a simple check;elseruns when the condition is false;else ifis for checking several options;switchis handy when you need to compare a value against several options;&&,||,!let you combine and flip conditions.
Positive, negative, or zero
Positive; if negative, print Negative; if zero, print Zero. Write a function Sign(n int) string that returns the message (no quotes).Sign(5) → PositiveSign(-13) → Negative-
Input:
Sign(5) -
Input:
Sign(-13) -
Input:
Sign(0) -
Input:
Sign(1) -
Input:
Sign(-100)
All digits different?
AllDigitsDifferent(n int) string that returns YES if all its digits are different, or NO otherwise.AllDigitsDifferent(237) → YESAllDigitsDifferent(117) → NO-
Input:
AllDigitsDifferent(237) -
Input:
AllDigitsDifferent(117) -
Input:
AllDigitsDifferent(123) -
Input:
AllDigitsDifferent(111) -
Input:
AllDigitsDifferent(989)
Which school?
SchoolByGrade(grade int) string. Grades 1–4 are Beginners School, grades 5–9 are Middle School, and grades 10–11 are High School.SchoolByGrade(2) → Beginners SchoolSchoolByGrade(10) → High School-
Input:
SchoolByGrade(2) -
Input:
SchoolByGrade(10) -
Input:
SchoolByGrade(1) -
Input:
SchoolByGrade(9) -
Input:
SchoolByGrade(11)
Larger of two numbers
MaxOfTwo(a int, b int) int that returns the value of the larger one.MaxOfTwo(17, 81) → 81MaxOfTwo(872, 976) → 976-
Input:
MaxOfTwo(17, 81) -
Input:
MaxOfTwo(872, 976) -
Input:
MaxOfTwo(5, 5) -
Input:
MaxOfTwo(-10, -20) -
Input:
MaxOfTwo(100, 1)
Largest of three numbers
MaxOfThree(a int, b int, c int) int that accepts three integers and returns the largest among them.MaxOfThree(17, 18, 19) → 19MaxOfThree(89, 221, 19) → 221-
Input:
MaxOfThree(17, 18, 19) -
Input:
MaxOfThree(89, 221, 19) -
Input:
MaxOfThree(5, 5, 5) -
Input:
MaxOfThree(-1, -2, -3) -
Input:
MaxOfThree(0, 100, 50)
Lesson 3: Loops & the for Statement
What is a loop?
A loop is a sequence of instructions that lets you perform an action many times.
In programming, you very often need to repeat the same action several times. For example:
- printing the numbers from 1 to 10;
- calculating the sum of a set of numbers;
- reading several values from the console;
- processing a list of students;
- printing a multiplication table.
Without loops you'd have to write the same code over and over by hand. Loops make code shorter, clearer, and easier to work with.
Go only has one loop — for — but it can take several different forms.
How does a loop work?
- first, a starting value is set;
- then the condition is checked;
- if the condition is true, the loop's body runs;
- after that, the value changes;
- then the condition is checked again.
The classic for
The classic for loop is used when you roughly know in advance how many times the loop needs to run.
For example, to print the numbers from 0 to 4:
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
0
1
2
3
4The loop is made up of three parts:
i := 0— initialization (the starting value)i < 5— condition (the loop keeps running while this is true)i++— step (updates the variable after each iteration)
So the loop ran 5 times — until i became equal to 5.
Example: printing 1 to 10
A very common task is printing numbers within a given range. A regular for loop handles that.
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
}
1
2
3
4
5
6
7
8
9
10In this example, i starts at 1. The loop runs as long as i <= 10. After each iteration, i increases by 1.
Example: sum of numbers
Loops are often used to accumulate a result. For example, we can add up the numbers from 1 to 5.
package main
import "fmt"
func main() {
sum := 0
for i := 1; i <= 5; i++ {
sum = sum + i
}
fmt.Println(sum)
}
15Here, the sum variable holds the running total. On each iteration we add the current value of i to it.
How sum changes:
- i = 1, sum = 0 + 1 = 1
- i = 2, sum = 1 + 2 = 3
- i = 3, sum = 3 + 3 = 6
- i = 4, sum = 6 + 4 = 10
- i = 5, sum = 10 + 5 = 15
Example: printing only even numbers
You can use conditionals inside a loop. For example, we can go through the numbers 1 to 10 and print only the even ones.
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i%2 == 0 {
fmt.Println(i)
}
}
}
2
4
6
8
10The condition i%2 == 0 checks whether a number divides evenly by 2. If the remainder is 0, the number is even.
for acting as while
Go doesn't have a separate while keyword. But you can use for as a while loop by leaving just the condition.
package main
import "fmt"
func main() {
i := 1
for i < 10 {
fmt.Println(i)
i++
}
}
1
2
3
4
5
6
7
8
9Same as before — the loop runs as long as the condition is true. Here, that means while i < 10.
The infinite loop
Writing for with no condition at all gives you an infinite loop. It keeps running forever until we stop it manually or use break.
package main
import "fmt"
func main() {
i := 1
for {
fmt.Println(i)
i++
if i > 5 {
break
}
}
}
1
2
3
4
5Important: use infinite loops carefully. If you forget the exit condition, the program can run forever.
The break statement
break lets you exit a loop early. It's useful once you've found what you needed and don't want to keep looping.
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i == 6 {
break
}
fmt.Println(i)
}
}
1
2
3
4
5Once i becomes 6, break fires. The loop ends immediately, so 6 and everything after it never gets printed.
The continue statement
continue skips the current iteration and jumps straight to the next one. It's useful when you need to skip certain values.
package main
import "fmt"
func main() {
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
fmt.Println(i)
}
}
1
2
4
5When i equals 3, continue runs, so fmt.Println(i) for that value gets skipped.
A loop that reads input
Loops can also be used to read data from the console. For example, we can keep reading numbers until the user enters 0.
package main
import "fmt"
func main() {
var number int
for {
fmt.Scan(&number)
if number == 0 {
break
}
fmt.Println(number)
}
}
In this example the loop runs forever, but ends once the user enters 0.
This approach is common when you don't know ahead of time how many values the user will enter.
Nested loops
A loop can live inside another loop. This is called a nested loop.
One of the most popular examples is a multiplication table.
package main
import "fmt"
func main() {
for i := 1; i < 10; i++ {
for j := 1; j < 10; j++ {
fmt.Print(i*j, "\t")
}
fmt.Println()
}
}
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81This gives us a multiplication table: i runs across, j runs down. Each loop runs while its variable is less than 10.
How it works:
- the outer loop is responsible for the rows;
- the inner loop is responsible for the columns;
- each iteration prints the result of i * j;
- once the inner loop finishes, we move to a new line.
Nested loop example: a rectangle of asterisks
Nested loops are often used to draw simple shapes in the console. For example, we can print a rectangle made of * characters.
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
for j := 1; j <= 5; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
*****
*****
*****The outer loop runs 3 times, creating 3 rows. The inner loop runs 5 times, printing 5 asterisks on each row.
The for range loop
Go frequently uses the for range form. It comes in handy when you need to go through a collection of data.
For example, we can go through a list of names:
package main
import "fmt"
func main() {
names := []string{"Aibek", "Dana", "Miras"}
for index, name := range names {
fmt.Println(index, name)
}
}
0 Aibek
1 Dana
2 MirasThe index variable holds the element number, and the name variable holds the value itself.
When you don't need the index
Sometimes you only need the value, and the index isn't needed. In that case you can use the _ symbol instead of the index.
package main
import "fmt"
func main() {
names := []string{"Aibek", "Dana", "Miras"}
for _, name := range names {
fmt.Println(name)
}
}
Aibek
Dana
MirasThe _ symbol means we don't need that value.
Common mistakes when working with loops
- forgetting to update the loop variable;
- writing a condition that is always true;
- creating an infinite loop with no break;
- getting the loop bounds wrong;
- mixing up < and <=.
Mini practice
To really get comfortable with loops, try writing programs for the following tasks:
- print the numbers from 1 to 100;
- print only the odd numbers from 1 to 50;
- calculate the sum of numbers from 1 to N;
- print the multiplication table for a single number;
- read numbers until the user enters 0;
- find the largest number among the ones entered;
- print a square made of
*characters.
Summary
Loops let you run the same code many times. Go only has the for loop for this, but it can be written in several forms:
- the classic
foris handy when the loop bounds are known; foraswhileis handy when only the condition check matters;- an infinite
foris used together withbreak; breakends the loop;continueskips the current iteration;- nested loops let you work with tables and shapes;
for rangeis used to walk through collections of data.
Count from n to m
RangeString(n int, m int) string that returns all the numbers from n to m, space-separated.RangeString(5, 10) → 5 6 7 8 9 10RangeString(2, 9) → 2 3 4 5 6 7 8 9-
Input:
RangeString(5, 10) -
Input:
RangeString(2, 9) -
Input:
RangeString(1, 3) -
Input:
RangeString(7, 7) -
Input:
RangeString(4, 1)
Even numbers up to n
EvensUpTo(n int) string that returns all the EVEN numbers from 0 to n, space-separated (an even number divides by 2 with no remainder).EvensUpTo(6) → 0 2 4 6EvensUpTo(10) → 0 2 4 6 8 10-
Input:
EvensUpTo(6) -
Input:
EvensUpTo(10) -
Input:
EvensUpTo(0) -
Input:
EvensUpTo(1) -
Input:
EvensUpTo(7)
Factorial with a for loop
Factorial(n int) int that returns the factorial of n, computed with a for loop.Factorial(5) → 120Factorial(0) → 1-
Input:
Factorial(5) -
Input:
Factorial(0) -
Input:
Factorial(1) -
Input:
Factorial(3) -
Input:
Factorial(6)
Numbers and their squares
NumbersAndSquares(n int) string that returns all the numbers from 1 to n together with their squares, one pair per line, as shown in the examples.NumbersAndSquares(3) → 1 1
2 4
3 9NumbersAndSquares(5) → 1 1
2 4
3 9
4 16
5 25-
Input:
NumbersAndSquares(3) -
Input:
NumbersAndSquares(5) -
Input:
NumbersAndSquares(1) -
Input:
NumbersAndSquares(2) -
Input:
NumbersAndSquares(4)
Sum from 1 to n
SumUpTo(n int) int that returns the sum of all the numbers from 1 to n (1 + 2 + 3 + ... + n).SumUpTo(3) → 6SumUpTo(5) → 15-
Input:
SumUpTo(3) -
Input:
SumUpTo(5) -
Input:
SumUpTo(1) -
Input:
SumUpTo(0) -
Input:
SumUpTo(10)
Lesson 4: Slices
What is a slice?
A slice is a sequential collection of elements of the same type. Unlike arrays, slices have a dynamic length: you can add new elements to them and remove existing ones.
Slices are very similar to arrays, but much more convenient in real development. While an array has a fixed size, a slice can grow as needed.
That's why in Go, people usually work with slices rather than plain arrays.
Slices are like a Kazakh toi (a big celebration): you can plan an event for 200 people, but how many guests actually show up in the end is anyone's guess. Maybe 150? 250? Or a full 300? Slices have the same quality as toi organizers — flexibility.
Meiram "Punctuality" Toibastarov — the guy who "never" shows up late, and knows a thing or two about slices
The main difference between a slice and an array — a slice's length can change while the program is running.
Array vs slice
Visually, an array and a slice look very similar, but there's an important difference between them.
Array
Fixed size. If an array is created for 5 elements, it will always contain exactly 5 elements.
Slice
Dynamic size. You can add elements to a slice and get a new, bigger slice.
In Go, a slice is written almost like an array, just without specifying a size inside the square brackets.
Creating a slice
Slices are created almost like arrays, just without specifying a length.
package main
import "fmt"
func main() {
boxers := []string{"Muhammad Ali", "Mike Tyson", "George Foreman", "Joe Frazier"}
for _, value := range boxers {
fmt.Println(value)
}
}
Muhammad Ali
Mike Tyson
George Foreman
Joe FrazierNotice: inside [] we didn't specify a length — that's exactly why this is a slice.
If you write [4]string, that's an array. If you write []string, that's a slice.
Slices of different types
A slice can store elements of different data types, but inside one slice all elements must be of the same type.
A slice of numbers:
numbers := []int{10, 20, 30, 40}
A slice of strings:
cities := []string{"Almaty", "Astana", "Paris"}
A slice of booleans:
answers := []bool{true, false, true}
Creating a slice with make()
Slices can also be created using the make() function:
package main
import "fmt"
func main() {
cities := make([]string, 4)
cities[0] = "Almaty"
cities[1] = "Astana"
cities[2] = "New-York"
cities[3] = "Washington"
for _, value := range cities {
fmt.Println(value)
}
}
Almaty
Astana
New-York
WashingtonWhen we write make([]string, 4), Go creates a slice of strings with length 4. After that, we can access elements by indices from 0 to 3.
Now let's look at how to add and remove elements, and what the slice operator is.
Length and capacity: len and cap
A slice has two important characteristics: length and capacity.
len— how many elements are currently in the slice;cap— how many elements fit in the current underlying array.
package main
import "fmt"
func main() {
numbers := []int{10, 20, 30}
fmt.Println(len(numbers))
fmt.Println(cap(numbers))
}
3
3At first, len and cap are often equal. But after adding elements with append, the capacity may change.
Adding elements (append)
The append(slice, value) function is used to add an element to a slice.
package main
import "fmt"
func main() {
cities := []string{"Almaty", "Astana"}
cities = append(cities, "Paris")
cities = append(cities, "Marcel")
for _, value := range cities {
fmt.Println(value)
}
}
Almaty
Astana
Paris
MarcelIt's important to save the result back into the variable: cities = append(cities, "Paris"). The append function returns a new slice.
Adding several elements
You can add several elements at once through append.
package main
import "fmt"
func main() {
numbers := []int{1, 2, 3}
numbers = append(numbers, 4, 5, 6)
fmt.Println(numbers)
}
[1 2 3 4 5 6]Adding one slice into another
Sometimes you need to add not one value, but all the elements of another slice. The ... operator is used for this.
package main
import "fmt"
func main() {
first := []int{1, 2, 3}
second := []int{4, 5, 6}
first = append(first, second...)
fmt.Println(first)
}
[1 2 3 4 5 6]The three dots ... unpack the second slice into a set of separate elements.
The slice operator (s[i:j])
The slice operator looks like this: s[i:j], where:
s— the source slicei— the starting index (inclusive)j— the ending index (exclusive)
| Index | Value |
|---|---|
| 0 | Almaty |
| 1 | Astana |
| 2 | New-York |
| 3 | Washington |
For example, if you write cities[0:2], you get the elements at indices 0 and 1. The element at index 2 is no longer included in the result.
package main
import "fmt"
func main() {
cities := []string{"Almaty", "Astana", "New-York", "Washington", "Paris", "Marcel"}
kzCities := cities[0:2]
usaCities := cities[2:4]
frCities := cities[4:6]
fmt.Println(kzCities) // Almaty, Astana
fmt.Println(usaCities) // New-York, Washington
fmt.Println(frCities) // Paris, Marcel
}
[Almaty Astana]
[New-York Washington]
[Paris Marcel]Short slice forms
In Go, you can leave out the starting or ending index.
s[:3]— take elements from the start up to index 3, not including 3;s[2:]— take elements from index 2 to the end;s[:]— take the whole slice.
package main
import "fmt"
func main() {
numbers := []int{10, 20, 30, 40, 50}
fmt.Println(numbers[:3])
fmt.Println(numbers[2:])
fmt.Println(numbers[:])
}
[10 20 30]
[30 40 50]
[10 20 30 40 50]Changing a slice element
Just like with arrays, slice elements can be changed by index.
package main
import "fmt"
func main() {
cities := []string{"Almaty", "Astana", "Paris"}
cities[2] = "London"
fmt.Println(cities)
}
[Almaty Astana London]Here, the element at index 2 was changed from Paris to London.
Removing an element
Removal is usually done by combining append with the slice operator. We take the elements before the index and the elements after the index, then "glue" them together.
package main
import "fmt"
func main() {
capitals := []string{"Astana", "Washington", "Marcel", "Paris"}
num := 2 // remove the element at index 2 (Marcel)
capitals = append(capitals[:num], capitals[num+1:]...)
fmt.Println(capitals)
}
[Astana Washington Paris]Important: capitals[:num] is the elements before num, and capitals[num+1:] is the elements after num. The three dots ... "unpack" the slice as a set of arguments.
How this works:
capitals[:2]gives[Astana Washington];capitals[3:]gives[Paris];appendjoins these parts;- the element
Marceldisappears from the result.
Looping over a slice with range
Slices are convenient to loop over with range. We can get both the index and the value.
package main
import "fmt"
func main() {
cities := []string{"Almaty", "Astana", "Paris"}
for index, value := range cities {
fmt.Println(index, value)
}
}
0 Almaty
1 Astana
2 ParisWhen you don't need the index
If you only need the element, you can replace the index with _.
package main
import "fmt"
func main() {
cities := []string{"Almaty", "Astana", "Paris"}
for _, value := range cities {
fmt.Println(value)
}
}
Almaty
Astana
ParisExample: sum of slice elements
Slices are often used together with loops. For example, we can calculate the sum of all the numbers in a slice.
package main
import "fmt"
func main() {
numbers := []int{10, 20, 30, 40}
sum := 0
for _, value := range numbers {
sum = sum + value
}
fmt.Println(sum)
}
100Example: finding the maximum number
Using a loop and a slice, we can find the maximum number.
package main
import "fmt"
func main() {
numbers := []int{15, 90, 12, 45, 7}
max := numbers[0]
for _, value := range numbers {
if value > max {
max = value
}
}
fmt.Println(max)
}
90Common mistakes when working with slices
- forgetting to save the result of
appendback into the variable; - accessing an index that doesn't exist in the slice;
- mixing up an array
[5]intand a slice[]int; - forgetting that the ending index in
s[i:j]is not included; - forgetting to add
...when adding one slice into another.
Mini practice
To get comfortable with slices, try writing programs for the following tasks:
- create a slice of 5 numbers and print all the elements;
- add a few more numbers to the slice using
append; - print only the first 3 elements of the slice;
- print the elements from index 2 to the end;
- remove an element by index;
- calculate the sum of all the elements;
- find the maximum number;
- print only the positive numbers.
Key takeaways
- a slice looks like an array, but has a dynamic length;
- a slice is created with
[]type, without specifying a size; makelets you create a slice of a given length;appendadds new elements;s[i:j]gives you part of a slice;- the ending index in
s[i:j]is not included; - elements can be changed by index;
- removal can be done using
appendand the slice operator; rangeis convenient for looping over a slice.
The 4th element
FourthElement(numbers []int) int that returns the 4th element of the slice (index 3).FourthElement([]int{41, -231, 24, 49, 6}) → 49FourthElement([]int{1, 2, 3, 4}) → 4-
Input:
FourthElement([]int{41, -231, 24, 49, 6}) -
Input:
FourthElement([]int{1, 2, 3, 4}) -
Input:
FourthElement([]int{10, 20, 30, 40, 50, 60}) -
Input:
FourthElement([]int{-5, -10, -15, -20}) -
Input:
FourthElement([]int{100, 200, 300, 400, 500})
Minimum and maximum
MinMax(numbers []int) string that returns the minimum and maximum values of the slice, space-separated, as shown in the examples.MinMax([]int{4, 9, 2, 5}) → 2 9MinMax([]int{1, 2, -1, 4, 5, 56, 7, 8, 9, -21}) → -21 56-
Input:
MinMax([]int{4, 9, 2, 5}) -
Input:
MinMax([]int{1, 2, -1, 4, 5, 56, 7, 8, 9, -21}) -
Input:
MinMax([]int{5}) -
Input:
MinMax([]int{-1, -2, -3}) -
Input:
MinMax([]int{100, 50, 75, 25, 0})
Difference between max and min
MaxMinDiff(numbers []int) int that returns the difference between the maximum and minimum elements of the slice.MaxMinDiff([]int{4, 9, 2, 5, 8, 7, 11, 16}) → 14MaxMinDiff([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) → 9-
Input:
MaxMinDiff([]int{4, 9, 2, 5, 8, 7, 11, 16}) -
Input:
MaxMinDiff([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) -
Input:
MaxMinDiff([]int{5}) -
Input:
MaxMinDiff([]int{-10, -5, -1}) -
Input:
MaxMinDiff([]int{100, 1})
Reverse the slice
Reverse(numbers []int) string that returns all the elements of the slice in reverse order, space-separated.Reverse([]int{6, 19, 26, -3, 46, 8, 5, -65, 90, 25}) → 25 90 -65 5 8 46 -3 26 19 6Reverse([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) → 10 9 8 7 6 5 4 3 2 1-
Input:
Reverse([]int{6, 19, 26, -3, 46, 8, 5, -65, 90, 25}) -
Input:
Reverse([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) -
Input:
Reverse([]int{1}) -
Input:
Reverse([]int{1, 2}) -
Input:
Reverse([]int{5, 10, 15, 20, 25})
Even elements only
EvensOnly(numbers []int) string that builds a second slice containing only the even elements of the first slice, and returns its elements space-separated.EvensOnly([]int{4, 9, 2, 5, 8, 7, 11, 16}) → 4 2 8 16EvensOnly([]int{6, 19, 26, 3, 46, 8, 5, 65, 90, 25}) → 6 26 46 8 90-
Input:
EvensOnly([]int{4, 9, 2, 5, 8, 7, 11, 16}) -
Input:
EvensOnly([]int{6, 19, 26, 3, 46, 8, 5, 65, 90, 25}) -
Input:
EvensOnly([]int{1, 3, 5, 7}) -
Input:
EvensOnly([]int{2, 4, 6, 8}) -
Input:
EvensOnly([]int{0, -2, -4, 3})