Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions internal/exercises/catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,32 @@ concepts:
test_regex: ".*"
hints:
- Define a custom error type and return it from a function.
- slug: 28_defer
title: Defer
test_regex: ".*"
hints:
- Use `f.close()` with `defer` keyword.
- slug: 29_go_routines
title: Go Routines
test_regex: ".*"
hints:
- Use `go` keyword to execute functions concurrently using go routines.
- slug: 30_channels
title: Channels
test_regex: ".*"
hints:
- Use `make(chan T)` to create a channel and `<-` to send and receive on it.
- slug: 31_mutexes
title: Mutexes
test_regex: ".*"
hints:
- Use `sync.Mutex` to synchronize access to a shared resource.
- slug: 32_sorting
title: Sorting
test_regex: ".*"
hints:
- Use `slice.Sort` for sorting slices.

- slug: 37_xml
title: XML Encoding and Decoding
test_regex: ".*"
Expand Down
29 changes: 29 additions & 0 deletions internal/exercises/solutions/28_defer/defer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package deferr

import (
"fmt"
"os"
"sync"
)

var (
fileInstance *os.File
once sync.Once
)

func CreateFile() *os.File {
once.Do(func() {
f, err := os.CreateTemp("", "example.txt")
if err != nil {
panic(err)
}
fileInstance = f
})
return fileInstance
}

func WriteToFile(*os.File) {
f := CreateFile()
defer f.Close()
fmt.Fprintln(f, "data")
}
25 changes: 25 additions & 0 deletions internal/exercises/solutions/29_go_routines/go_routines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package go_routines

import (
"time"
)

func sleepForOneHundredMillisecond() {
time.Sleep(100 * time.Millisecond)
}

func sleepForTwoHundredMilliseconds() {
time.Sleep(200 * time.Millisecond)
}

func RunConcurrently() {
go sleepForOneHundredMillisecond()
go sleepForTwoHundredMilliseconds()

// go func() {
// sleepForOneHundredMillisecond()
// }()
// go func() {
// sleepForTwoHundredMilliseconds()
// }()
}
13 changes: 13 additions & 0 deletions internal/exercises/solutions/30_channels/channels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package channels

func ReadMessage() string {
chat := make(chan string)
go func() {
senderMessage := "Hi!"
chat <- senderMessage
}()

msg := <-chat

return msg
}
31 changes: 31 additions & 0 deletions internal/exercises/solutions/31_mutexes/mutexes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package mutexes

import "sync"

const numWorkers = 10_000

func Counting() int {

count := 0
done := make(chan bool, numWorkers)
var wg sync.WaitGroup
var mu sync.Mutex

for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
count += 1
mu.Unlock()
done <- true
}()
}

for i := 1; i <= numWorkers; i++ {
<-done
}

wg.Wait()
return count
}
18 changes: 18 additions & 0 deletions internal/exercises/solutions/32_sorting/sorting.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package sorting

import (
"slices"
)

var Years = []int{2017, 2003, 2026}
var Pets = []string{"Dog", "Cat", "Parrot"}
Comment on lines +7 to +8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Refactor to avoid mutable global state.

Exported package-level variables that are mutated by the sorting functions create several issues:

  1. Non-idempotent behavior: After the first call, the data is already sorted, making subsequent calls redundant.
  2. Test reliability: If tests run multiple times or in parallel, shared mutable state can cause unpredictable results.
  3. Poor practice demonstration: For an educational exercise, this pattern teaches anti-patterns rather than best practices.

Consider refactoring to accept input parameters and return new sorted slices:

-var Years = []int{2017, 2003, 2026}
-var Pets = []string{"Dog", "Cat", "Parrot"}
-
-func SortNumbers() []int {
-	slices.Sort(Years)
-	return Years
+func SortNumbers(numbers []int) []int {
+	sorted := make([]int, len(numbers))
+	copy(sorted, numbers)
+	slices.Sort(sorted)
+	return sorted
 }
 
-func SortAnimals() []string {
-	slices.Sort(Pets)
-	return Pets
+func SortAnimals(animals []string) []string {
+	sorted := make([]string, len(animals))
+	copy(sorted, animals)
+	slices.Sort(sorted)
+	return sorted
 }

Alternatively, if in-place sorting is the intended teaching point, document this behavior clearly and accept the slice as a parameter.

🤖 Prompt for AI Agents
internal/exercises/solutions/32_sorting/sorting.go lines 7-8: the file currently
defines exported package-level mutable variables Years and Pets which causes
non-idempotent behavior and test flakiness; refactor by removing these exported
globals and instead implement functions that accept a slice parameter and return
a sorted copy (or, if demonstrating in-place sorting, accept a slice parameter
and clearly document/make the function name reflect that it mutates the input),
ensure you do not mutate package state, and update callers/tests to pass slices
and receive sorted results (or pass slices explicitly when in-place).


func SortNumbers() []int {
slices.Sort(Years)
return Years
}

func SortAnimals() []string {
slices.Sort(Pets)
return Pets
}
29 changes: 29 additions & 0 deletions internal/exercises/templates/28_defer/defer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package deferr

import (
"fmt"
"os"
"sync"
)

var (
fileInstance *os.File
once sync.Once
)

func CreateFile() *os.File {
once.Do(func() {
f, err := os.CreateTemp("", "example.txt")
if err != nil {
panic(err)
}
fileInstance = f
})
return fileInstance
}

func WriteToFile(*os.File) {
f := CreateFile()
// TODO: Add your code here
fmt.Fprintln(f, "data")
}
17 changes: 17 additions & 0 deletions internal/exercises/templates/28_defer/defer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package deferr

import (
"fmt"
"testing"
)

func TestClosingFileAfterWriting(t *testing.T) {
f := CreateFile()
WriteToFile(f)

// trying to write to the file after closing
_, err := fmt.Fprintln(f, "data")
if err == nil {
t.Fatal("File wasn't closed!")
}
}
19 changes: 19 additions & 0 deletions internal/exercises/templates/29_go_routines/go_routines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package go_routines

import (
"time"
)

func sleepForOneHundredMillisecond() {
time.Sleep(100 * time.Millisecond)
}

func sleepForTwoHundredMilliseconds() {
time.Sleep(200 * time.Millisecond)
}

func RunConcurrently() {
// TODO: update following code
sleepForOneHundredMillisecond()
sleepForTwoHundredMilliseconds()
}
24 changes: 24 additions & 0 deletions internal/exercises/templates/29_go_routines/go_routines_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package go_routines

import (
"testing"
"time"
)

func TestGoRoutines(t *testing.T) {
start := time.Now()
RunConcurrently()
elapsed := time.Since(start)

// according to benchmark it is about ~2 microseconds
// x2 for more breathing room, so 4
if elapsed.Microseconds() >= 4 {
t.Fatal("Go routines was not used!")
}
}

func BenchmarkRunConcurrently(b *testing.B) {
for i := 0; i < b.N; i++ {
RunConcurrently()
}
}
15 changes: 15 additions & 0 deletions internal/exercises/templates/30_channels/channels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package channels

import "fmt"

func ReadMessage() string {
// TODO: update following code
// to receive senderMessage in main goroutine
// and return it from ReadMessage function
go func() {
senderMessage := "Hi!"
fmt.Println(senderMessage)
}()

return ""
}
12 changes: 12 additions & 0 deletions internal/exercises/templates/30_channels/channels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package channels

import "testing"

func TestReadMessage(t *testing.T) {
got := ReadMessage()
want := "Hi!"

if got != want {
t.Errorf("Expected %s, got %s", want, got)
}
}
30 changes: 30 additions & 0 deletions internal/exercises/templates/31_mutexes/mutexes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package mutexes

import "sync"

const numWorkers = 10_000

func Counting() int {

// TODO: update following code to avoid race conditions
// using sync.Mutex
count := 0
done := make(chan bool, numWorkers)
var wg sync.WaitGroup

for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
count += 1
done <- true
}()
Comment on lines +15 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Synchronize the shared counter to avoid races.

count += 1 runs concurrently without a mutex, so increments are lost and Counting() can return less than numWorkers, making the template fail its own test. Please guard the increment with a sync.Mutex.

 	count := 0
 	done := make(chan bool, numWorkers)
 	var wg sync.WaitGroup
+	var mu sync.Mutex
 
 	for i := 1; i <= numWorkers; i++ {
 		wg.Add(1)
 		go func() {
 			defer wg.Done()
-			count += 1
+			mu.Lock()
+			count++
+			mu.Unlock()
 			done <- true
 		}()
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
count += 1
done <- true
}()
count := 0
done := make(chan bool, numWorkers)
var wg sync.WaitGroup
var mu sync.Mutex
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
count++
mu.Unlock()
done <- true
}()
}
🤖 Prompt for AI Agents
In internal/exercises/templates/31_mutexes/mutexes.go around lines 15 to 21, the
concurrent increment "count += 1" is not synchronized which causes data races
and lost increments; declare a sync.Mutex (e.g., var mu sync.Mutex) in the
surrounding scope and inside the goroutine lock the mutex before incrementing
and unlock it afterward (mu.Lock(); count += 1; mu.Unlock()), leaving wg.Done
and done <- true as-is so each worker still signals completion.

}

for i := 1; i <= numWorkers; i++ {
<-done
}

wg.Wait()
return count
}
12 changes: 12 additions & 0 deletions internal/exercises/templates/31_mutexes/mutexes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mutexes

import "testing"

func TestCounter(t *testing.T) {
got := Counting()
want := numWorkers

if got != want {
t.Fatalf("Expected %d, got %d", want, got)
}
}
14 changes: 14 additions & 0 deletions internal/exercises/templates/32_sorting/sorting.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package sorting

var Years = []int{2017, 2003, 2026}
var Pets = []string{"Dog", "Cat", "Parrot"}

func SortNumbers() []int {
// TODO: sort Years
return Years
}

func SortAnimals() []string {
// TODO: sort Pets
return Pets
}
22 changes: 22 additions & 0 deletions internal/exercises/templates/32_sorting/sorting_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package sorting

import (
"slices"
"testing"
)

func TestSorting(t *testing.T) {
t.Run("Sorting numbers", func(t *testing.T) {
SortNumbers()
if !slices.IsSorted(Years) {
t.Fatal("Strings are not sorted!")
}
})

t.Run("Sorting strings", func(t *testing.T) {
SortAnimals()
if !slices.IsSorted(Pets) {
t.Fatal("Strings are not sorted!")
}
})
}
Loading