go-exercises

Go Exercises and Questions

OTHER License

Stars
163

Go Exercise

ℹ️  This repo contains questions and exercises to learn and practice Golang

📊  There are currently 98 exercises and questions

Exercises

Hello World

Name Exercise Solution Comments
Hello World! Exercise Solution
Variables & Constants Exercise Solution
Arithmetic Operators Exercise Solution
Data Types Exercise Solution
User Input Exercise Solution
Packages Exercise Solution
Logical Operators Exercise Solution
Conditionals Exercise Solution
Switch Exercise Solution

Strings Exercises

Name Exercise Solution Comments
Split Strings Exercise Solution

Arrays and Slices Exercises

Name Exercise Solution Comments
Arrays 101 Exercise Solution
Slices 101 Exercise Solution

Maps Exercises

Name Exercise Solution Comments
Maps 101 Exercise Solution

Loops Exercises

Name Exercise Solution Comments
Loops 101 Exercise Solution
Loops 102 Exercise Solution
Continue Exercise Solution

Functions Exercises

Name Exercise Solution Comments
Functions 101 Exercise Solution
Named Return Values Exercise Solution

Generics Exercises

Name Exercise Solution Comments
Generics 101 Exercise Solution

Questions

Go 101

  • Strong and static typing - the type of the variables can't be changed over time and they have to be defined at compile time
  • Simplicity
  • Fast compile times
  • Built-in concurrency
  • Garbage collected
  • Platform independent
  • Compile to standalone binary - anything you need to run your app will be compiled into one binary. Very useful for version management in run-time.

True

Any exported variable, function, ... begins with a capital letter. In fact when using a package, you can use only the things the package exports for you and others to use.

Variables and Data Types

package main

import "fmt"

func main() {
  x := 2
  var y int = 2

  fmt.Printf("x: %v. y: %v", x, y)
}
  • In Go we can redeclare variables
  • Once a variable declared, we must use it
  • False
  • True
package main

import "fmt"

func main() {
    var userName
    userName = "user"
    fmt.Println(userName)
}

Error. The type userName is not defined. It has to be declared or the value assignment should happen at declaration.

So both var userName = user and var userName string are valid.

The result is the same, a variable with the value 2.

With var x int = 2 we are setting the variable type to integer, while with x := 2 we are letting Go figure out by itself the type. The result is the same, both styles can't be used in every situation. For example, short declaration can't be used outside of a function.

package main

import "fmt"

x := 2

func main() {
    x = 3
    fmt.Println(x)
}

It will fail with expected declaration, found x as outside of a function, every statement should start with a keyword (and short variable declarations won't work).

package main

import "fmt"

var (
  x bool   = false
  y int    = 0
  z string = "false"
)

func main() {
  fmt.Printf("The type of x: %T. The value of x: %v\n", x, x)
  fmt.Printf("The type of y: %T. The value of y: %v\n", y, y)
  fmt.Printf("The type of z: %T. The value of z: %v\n", y, y)
}

Package level variables are variables that defined at the package level instead of a specific function (like main for example).

If you have multiple functions in your package that use the same variables, it might make these variables available to the functions by defining them at the package level. This can be in the same file or any file under the same package. For example:

package main

var packageLevelVar int = 0 

func main() {
  nonPackageLevelVar := 0
}

Conversion

func main() {
    var x float32 = 13.5
    var y int
    y = x
}
package main

import "fmt"

func main() {
    var x int = 2
    fmt.Println(x)
    var y float32 = float32(x)                
    fmt.Println(y)
}
package main

import "fmt"

func main() {
    var x int = 101
    var y string
    y = string(x)
    fmt.Println(y)
}

It looks what unicode value is set at 101 and uses it for converting the integer to a string. If you want to get "101" you should use the package "strconv" and replace y = string(x) with y = strconv.Itoa(x)

Integers

package main
 
import "fmt"
 
func main() {
    var x uint
    x = -3
    // comment!
    fmt.Println(x)
}

Error. When x is declared as uint it means you can't put any negative values. But because we did put a negative value (-3) it will fail to compile it.

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println("A random integer:", rand.Intn(10))
}

Constants

  • Single constant
  • Multiple constants in one block
// Single constant
const x int = 2732

// Multiple constants in one block
const (
  y = 2017
  z = 2022
)
package main

func main() {
    var x int = 2
    var y int = 3
    const someConst = x + y
}
package main

import "fmt"

func main() {
  const X := 2
  fmt.Print(X)
}

It won't run. Constants cannot be declared using :=.

package main

import "fmt"

const (
    Const1 = 1 
    Const2 = 1 < 2 
    Const3 = 1 << 10                              
)

func main() {
    fmt.Println(Const1)
    fmt.Println(Const2)
    fmt.Println(Const3)
}

1 true 1024

The 1024 result is because we shifted a 1 bit left 10 places.

Logical Operators

package main

import "fmt"

func main() {
    x := 2017

    fmt.Println(x > 2022 && x < 3000)
    fmt.Println(x > 2000 && x < 3000)
    fmt.Println(x > 2000 && x&2 == 0)              
}
false
true
false
package main

import "fmt"

func main() {
    x := 2017

    fmt.Println(x > 2022 || x < 3000)
    fmt.Println(x > 2000 || x < 3000)
    fmt.Println(x > 2000 || x&2 == 0)              
}
true
true
true
package main
        
import "fmt"
        
func main() {
    x := true
        
    fmt.Println(x)
    fmt.Println(!x)
    fmt.Println(!x && x)
    fmt.Println(!x || x)                   
}
true
false
false
true

Strings

var some_string := "Hello, World"
Hello,
World
var some_string := "Hello,\nWorld"
package main

import "fmt"
        
func main() {
    some_string := `Hello,\nWorld`

    fmt.Println(some_string)
}
package main
 
import "fmt"
 
func main() {
    some_string := "There"
                              
    fmt.Println("Hello", some_string)
}

Hello There

package main                         

import "fmt"

func main() {
    str := "Hello, world!"
    fmt.Println(len(str))
}
var str string = "cowabunga"
fmt.Println(str[3])

97 Because it prints the ascii code of 'a' which is 97.

  • fmt.Println(str[3:5])

'ab'

Conditionals

x := 5
if x > 0 {
  fmt.Print("It's bigger than  0!")
}

rand.Seed(time.Now().UnixNano())
var x int = rand.Intn(100)
if x > 10 {
  fmt.Print("yay!")
} else {
  fmt.Print("nay!")
}
package main
  
import ( 
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    if randNum := rand.Intn(100); randNum%2 == 0 {
        fmt.Print("Bingo!")               
    } else {
        fmt.Print(randNum)
    }       
}

Defines a variable with random value between 0 and 100. If the random value is even, prints "Bingo!" otherwise, prints the random value itself.

Switch

package main

import (
	"fmt"
	"time"
)

func main() {

    today := time.Now().Weekday().String()

    switch today {
    case "Sunday":
      fmt.Println("Here we go")
    case "Monday":
      fmt.Println("We have just started")
    default:
      fmt.Println(today)
	}
}

User Input

var name string

fmt.Scan(name)

fmt.Println(name)
var name string

fmt.Scan(&name)

fmt.Println(name)

Because we want to reference the memory address of the variable, this is where the user input will be stored.

var age int = 3

fmt.Println(age)
fmt.Println(&age)

The value of age variable and then the memory location of age variable

Arrays and Slices

var x [4]int
var rgb = [3]string{"red", "green", "blue"}
[0 0 0 0 0 0 0 0 0 0]
var arr = [10]int{5, 9: 100}
var arr = [...]int{1, 2, 3, 4}
var anotherArr = [4]int{1, 2, 3, 4}
fmt.Println(arr == anotherArr)

The result of comparison is true.

fmt.Println(len(arr))

False. Go treats the array size as being part of the type itself. So [1]int type != [2]int type.

False. It's not possible to use variable to define an array of certain size. As the size of an array is part of its type and types must be resolved at compile time, not runtime.

  • without values (with nil)
  • with two values

var slice []int var slice = []int{1, 2}

b = append(b, 7)

[2 0 1 7 2 0 2 2]

package main

import (
    "fmt"
    "reflect"
)

func main() {
    x := []int {2, 0, 1, 7}
    y := []int {2, 0, 2, 2}

    fmt.Println(reflect.DeepEqual(x, y))
}

Because in Go when you pass a something to a function, it copies it. So when we append something to a slice it appends it to a copy of it. So in order to change the original slice, we need to assign the returned copy back into the original slice.

slice := make([]int, 20)

slice := make([]int, 3, 20)

It's more efficent to set the size once for a slice rather than increase its capacity every times new items added to it.

slice := make([]int, 3)
slice = append(slice, 41)

[0 0 0 41]

True. Otherwise it will cause compile-time error or run-time error, depends on how you defined it.

  • slice[:]
  • slice[2:]
  • slice[:1]
  • slice[2:4]
  • [1 2 3 4 5]
  • [3 4 5]
  • [1]
  • [3 4]
slice := []int{1, 2, 3}
anotherSlice := slice[1:]
slice[1] = 999
anotherslice[1] = 5
fmt.Println("slice:", slice)
fmt.Println("slice:", anotherSlice)
[10 999 3]
[999 5]

The expalantion is that slicing a slice isn't creating a copy of the data but rather creates another variable that shares the very same memory.

When creating a slice of a slice, it will create variable that shares the same memory. To create a slice that is indepdant of the original slice, you can use the built-in function copy

slice := []int{1, 2, 3}
destSlice = make([]int, 3)
numOfElements := copy(slice, destSlice)

Loops

  • variable i initialized to 0
  • expression where the variable should be smaller than 50
  • at the end of each iteration the variable value should be incremented by 1
  • In the loop itself, the value of variable i should be added to a variable called sum (initialize the variable before the loop with value of 0)
sum := 0
for i := 0; i < 50; i++ {
    sum += i
}
for {
    fmt.Print("forever!")
}
for {
    fmt.Print("forever!")
}

for true {
    fmt.Print("forever!")
}

From result perspective, there is no difference. Both are infinite loops.

Maps

  • an empty map where all keys are of string type, as well as the values
  • an empty map where all the keys are of string type and the values are of int type
  • A map with string type keys and int array values (non empty map, populate it with data)
  • Empty map with int type keys and string type values of size 100

var someMap = map[string]string

anotherMap := map[string]int{}

nonEmptyMap := map[string][]int{
  "someKey": []int{1, 2, 3},
  "anotherKey": []int{4, 5, 6},
}

someMap := make(map[int][]string, 100)

True. This is also true for the all the values in a single map.

len(someMap)

False.

someMap := map[string]int{}
someMap["x"] = 41
fmt.Println(someMap["y"])

0

someMap := map[string]int{
  "x": 41,
}
value, exists := someMap["x"]
fmt.Println(value, exists)
value, exists = someMap["y"]
fmt.Println(value, exists)

41 true 0 false

someMap := map[string]int{
  "x": 41,
  "y": 303,
  "z": 56,
}

delete(someMap, "y")

Functions

func PrintHelloWorld() {
    fmt.Println("Hello World")
}
func add(x int, y int) int {
    return x + y
}

add is a function that takes two parameters (two integers) and returns their sum.

func add(x int, y int) int {
    return x + y
}

func add(x, y int) int {
    return x + y
}

Both. If two or more parameters share the same type, you can specify it only once.

False. A fucntion in Go can return multiple values

func swap(x, y string) (string, string) {
    return y, x
}
x, y = swap("and roll", "rock")
fmt.Println(a, b)
func process(num int) (x int) {
    x = num + 10
    var z = num - x
    x = z + x
    return
}

func main() {
  fmt.Println(process(10))
}

10

Defer

package main

import "fmt"

func main() {
    defer fmt.Println("1")

    fmt.Println("2")
}
2
1

2 is printed before 1 due to the defer statement. The defer statement defers the execution of the function (fmt.Println("1")) until the surrounding fucntion (which is main()) returns.

True.

package main

import "fmt"

func main() {
    fmt.Println("100")

    for i := 0; i < 10; i++ {
      defer fmt.Println(i)
    }

    fmt.Println("200")
  }
100
200
9
8
7
6
5
4
3
2
1
0

Deferred functions are executed in a last-in-first-out order, this is why it prints it from 9 to 0 instead of from 0 to 9.

Packages

Capitalize the first letter of what you would like to export.

  • Local
  • Package
  • Global

Generics

By creating an interface.

package main

type C interface {
	int | int32 | string
}
package main

import "fmt"

type C interface {
  int | int32 | string
}

func show[T C](input T) {
	fmt.Println(input)
}

Because we created show function as a generic function. Therefore it can get types that we have in C.

package main

import "log"

func do_log(input string) {
	log.Print(input)
}

Use any keyword.

package main

import "log"

func do_log(input any) {
	log.Print(input)
}

Projects

Name Description Solution Comments
Simple Web Server Description Solution

Credits

Images

Go Gopher was designed by Renee French. The specific Go Gophers designs used in this project created by Arie Bregman

Package Rankings
Top 9.42% on Proxy.golang.org