Skip to content

Commit 3d94df9

Browse files
authored
Merge branch 'main' into rate_limiting
Signed-off-by: Kaushalya Pradeep <24698778+kaushalyap@users.noreply.github.com>
2 parents 48d5197 + 0ddf62f commit 3d94df9

13 files changed

Lines changed: 703 additions & 1 deletion

File tree

internal/exercises/catalog.yaml

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,27 @@ concepts:
197197
hints:
198198
- Use `select` to handle multiple channel operations concurrently.
199199
- Use `time.After` to simulate a 5 microsecond timeout.
200+
- slug: 41_time_delay
201+
title: "Delays and Timers in Go"
202+
difficulty: beginner
203+
topics: ["time", "sleep", "channels", "timer"]
204+
test_regex: ".*"
205+
hints:
206+
- "Use time.Sleep() to pause execution for a duration."
207+
- "Convert milliseconds to time.Duration using time.Millisecond."
208+
- "Use a buffered channel to send a signal after waiting."
209+
- "Use time.After or time.Sleep to trigger events after delays."
210+
- slug: 42_wait_group
211+
title: WaitGroups
212+
test_regex: ".*"
213+
difficulty: beginner
214+
topics: ["concurrency", "sync", "goroutines"]
215+
hints:
216+
- "Use sync.WaitGroup to wait for all launched goroutines."
217+
- "Call wg.Add(1) before starting a goroutine and wg.Done() when it finishes."
218+
- "Close result channels after wg.Wait() so collectors can range over them."
219+
- "Capture loop variables correctly inside goroutines (e.g., `n := n`)."
220+
- "Return results as a slice — order does not need to match input order
200221
projects:
201222
- slug: 101_text_analyzer
202223
title: Text Analyzer (Easy)
@@ -276,6 +297,30 @@ projects:
276297
- "Use time.LoadLocation() to work with different timezones"
277298
- "Extract time components using .Date() and .Clock() methods"
278299

300+
- slug: 39_panic
301+
title: Panic and Recover
302+
test_regex: ".*"
303+
hints:
304+
- "Use `panic()` to simulate runtime errors when appropriate."
305+
- "Use `defer` with `recover()` to catch and handle panics."
306+
- "Recovering from panics allows graceful handling of unexpected situations."
307+
- "Remember: `recover()` only works inside a deferred function."
308+
309+
- slug: 64_timers
310+
title: "Timers"
311+
difficulty: medium
312+
topics: ["time", "goroutines", "concurrency", "synchronization"]
313+
test_regex: ".*"
314+
hints:
315+
- "Use a map[string]*time.Timer to track active timers by key."
316+
- "Use a mutex to safely access the timers map concurrently."
317+
- "Start a timer with time.AfterFunc(d, fn) to run a callback after duration d."
318+
- "If Start is called again for an existing key, stop and replace the old timer."
319+
- "Stop should remove the timer from the map and prevent the callback from firing."
320+
- "Reset can call timer.Reset(d) to reschedule the same callback."
321+
- "Remember to remove timers from the map after they fire to avoid memory leaks."
322+
- "Write tests to verify Start, Stop, and Reset behavior, including concurrency scenarios."
323+
279324
- slug: 68_rate_limiting
280325
title: Rate Limiting
281326
test_regex: ".*"
@@ -288,4 +333,4 @@ projects:
288333
- "Reset should clear the request history for a key; if the key does not exist, do nothing."
289334
- "Consider edge cases: multiple keys, concurrent access, and requests exactly at the interval boundary."
290335
- "In tests, use time.Sleep with a small buffer above the interval to avoid flakiness."
291-
- "Initialize the timestamps map in NewRateLimiter to prevent nil pointer errors."
336+
- "Initialize the timestamps map in NewRateLimiter to prevent nil pointer errors."
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package panicex
2+
3+
// SafeDivision divides a by b, recovers from panic if denominator is zero
4+
func SafeDivision(a, b int) (result int) {
5+
defer func() {
6+
if r := recover(); r != nil {
7+
result = 0
8+
}
9+
}()
10+
if b == 0 {
11+
panic("divide by zero")
12+
}
13+
return a / b
14+
}
15+
16+
// TriggerMultiplePanics demonstrates defer + recover in a loop
17+
func TriggerMultiplePanics(nums []int) []string {
18+
results := make([]string, len(nums))
19+
for i, v := range nums {
20+
func(idx, val int) {
21+
defer func() {
22+
if r := recover(); r != nil {
23+
results[idx] = "recovered panic"
24+
}
25+
}()
26+
if val < 0 {
27+
panic("panic for negative")
28+
}
29+
results[idx] = "ok"
30+
}(i, v)
31+
}
32+
return results
33+
}
34+
35+
// PanicWithMessage panics with a custom message
36+
func PanicWithMessage(msg string) {
37+
panic(msg)
38+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package timedelay
2+
3+
// Real implementations using time.Now, time.Add and comparisons for the exercise.
4+
5+
import (
6+
"errors"
7+
"time"
8+
)
9+
10+
// WaitFor pauses execution until roughly ms milliseconds have passed.
11+
func WaitFor(ms int) {
12+
target := time.Now().Add(time.Duration(ms) * time.Millisecond)
13+
for time.Now().Before(target) {
14+
time.Sleep(1 * time.Millisecond)
15+
}
16+
}
17+
18+
// NotifyAfter returns a channel that receives true after roughly ms milliseconds.
19+
func NotifyAfter(ms int) chan bool {
20+
ch := make(chan bool, 1)
21+
target := time.Now().Add(time.Duration(ms) * time.Millisecond)
22+
23+
go func() {
24+
for time.Now().Before(target) {
25+
time.Sleep(1 * time.Millisecond)
26+
}
27+
ch <- true
28+
}()
29+
30+
return ch
31+
}
32+
33+
// WaitUntil blocks until the provided target time is reached (returns immediately if in the past).
34+
func WaitUntil(target time.Time) {
35+
for time.Now().Before(target) {
36+
time.Sleep(1 * time.Millisecond)
37+
}
38+
}
39+
40+
// NotifyAt returns a channel that receives true when target time is reached (sends immediately if in the past).
41+
func NotifyAt(target time.Time) chan bool {
42+
ch := make(chan bool, 1)
43+
44+
go func() {
45+
for time.Now().Before(target) {
46+
time.Sleep(1 * time.Millisecond)
47+
}
48+
ch <- true
49+
}()
50+
51+
return ch
52+
}
53+
54+
// ElapsedMillis returns how many milliseconds have elapsed since t.
55+
func ElapsedMillis(t time.Time) int64 {
56+
return time.Since(t).Milliseconds()
57+
}
58+
59+
// WaitForOrTimeout waits up to ms milliseconds but fails if total waiting exceeds timeoutMs.
60+
func WaitForOrTimeout(ms int, timeoutMs int) error {
61+
target := time.Now().Add(time.Duration(ms) * time.Millisecond)
62+
deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
63+
64+
for {
65+
now := time.Now()
66+
if !now.Before(target) {
67+
return nil
68+
}
69+
if !now.Before(deadline) {
70+
return errors.New("timeout exceeded before target reached")
71+
}
72+
time.Sleep(1 * time.Millisecond)
73+
}
74+
}
75+
76+
// ScheduleAfter runs fn after roughly ms milliseconds and returns a channel closed when fn finishes.
77+
func ScheduleAfter(ms int, fn func()) chan struct{} {
78+
done := make(chan struct{})
79+
target := time.Now().Add(time.Duration(ms) * time.Millisecond)
80+
81+
go func() {
82+
for time.Now().Before(target) {
83+
time.Sleep(1 * time.Millisecond)
84+
}
85+
fn()
86+
close(done)
87+
}()
88+
89+
return done
90+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package waitgroup
2+
3+
import "sync"
4+
5+
func Squares(nums []int) []int {
6+
length := len(nums)
7+
if length == 0 {
8+
return []int{}
9+
}
10+
11+
ch := make(chan int, length)
12+
var wg sync.WaitGroup
13+
wg.Add(length)
14+
15+
for _, n := range nums {
16+
n := n
17+
go func() {
18+
defer wg.Done()
19+
ch <- n * n
20+
}()
21+
}
22+
23+
go func() {
24+
wg.Wait()
25+
close(ch)
26+
}()
27+
28+
out := make([]int, 0, length)
29+
for v := range ch {
30+
out = append(out, v)
31+
}
32+
return out
33+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package timers
2+
3+
import (
4+
"sync"
5+
"time"
6+
)
7+
8+
type TimerManager struct {
9+
mu sync.Mutex
10+
timers map[string]*time.Timer
11+
}
12+
13+
// NewTimerManager returns a new TimerManager with an initialized timers map.
14+
func NewTimerManager() *TimerManager {
15+
return &TimerManager{
16+
timers: make(map[string]*time.Timer),
17+
}
18+
}
19+
20+
// Start starts a timer for the given key to run the provided callback after duration d.
21+
// If a timer with the same key already exists, it should be stopped and replaced.
22+
func (tm *TimerManager) Start(key string, d time.Duration, fn func()) {
23+
tm.mu.Lock()
24+
defer tm.mu.Unlock()
25+
26+
if oldTimer, ok := tm.timers[key]; ok {
27+
oldTimer.Stop()
28+
delete(tm.timers, key)
29+
}
30+
31+
timer := time.AfterFunc(d, func() {
32+
tm.mu.Lock()
33+
delete(tm.timers, key)
34+
tm.mu.Unlock()
35+
36+
fn()
37+
})
38+
39+
tm.timers[key] = timer
40+
}
41+
42+
// Stop stops and removes the timer with the given key.
43+
// It returns true if the timer existed and was stopped, false otherwise.
44+
func (tm *TimerManager) Stop(key string) bool {
45+
tm.mu.Lock()
46+
defer tm.mu.Unlock()
47+
48+
if timer, ok := tm.timers[key]; ok {
49+
timer.Stop()
50+
delete(tm.timers, key)
51+
return true
52+
}
53+
54+
return false
55+
}
56+
57+
// Reset resets the existing timer for the given key to expire after duration d again.
58+
// If no timer exists for that key, do nothing.
59+
func (tm *TimerManager) Reset(key string, d time.Duration) {
60+
tm.mu.Lock()
61+
defer tm.mu.Unlock()
62+
63+
if timer, ok := tm.timers[key]; ok {
64+
timer.Reset(d)
65+
}
66+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package panicex
2+
3+
// TODO:
4+
// - Implement SafeDivision to divide two numbers and recover from panic if denominator is zero
5+
// - Implement TriggerMultiplePanics to demonstrate defer + recover in a loop
6+
// - Implement PanicWithMessage to panic with a custom message
7+
8+
func SafeDivision(a, b int) (result int) {
9+
return 0
10+
}
11+
12+
func TriggerMultiplePanics(nums []int) []string {
13+
return nil
14+
}
15+
16+
func PanicWithMessage(msg string) {
17+
// panic with the given message
18+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package exercises
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestSafeDivision(t *testing.T) {
9+
t.Run("Normal Division", func(t *testing.T) {
10+
result := SafeDivision(10, 2)
11+
if result != 5 {
12+
t.Errorf("SafeDivision(10, 2) = %d, want 5", result)
13+
}
14+
})
15+
16+
t.Run("Division By Zero", func(t *testing.T) {
17+
result := SafeDivision(10, 0)
18+
if result != 0 {
19+
t.Errorf("SafeDivision(10, 0) = %d, want 0 (safe recovery)", result)
20+
}
21+
})
22+
}
23+
24+
func TestTriggerMultiplePanics(t *testing.T) {
25+
nums := []int{3, -1, 4, -2}
26+
results := TriggerMultiplePanics(nums)
27+
28+
if results == nil {
29+
t.Log("Placeholder implementation detected")
30+
return
31+
}
32+
33+
if len(results) != len(nums) {
34+
t.Errorf("Expected %d results, got %d", len(nums), len(results))
35+
}
36+
37+
for i, num := range nums {
38+
if num < 0 && results[i] == "" {
39+
t.Errorf("Expected non-empty result (recovery message) for negative number at index %d", i)
40+
}
41+
}
42+
}
43+
44+
func TestPanicWithMessage(t *testing.T) {
45+
defer func() {
46+
if r := recover(); r != nil {
47+
msg, ok := r.(string)
48+
if !ok {
49+
t.Errorf("Recovered panic is not a string: %v", r)
50+
}
51+
if !strings.Contains(msg, "expected panic") {
52+
t.Errorf("Panic message %q does not contain expected text", msg)
53+
}
54+
} else {
55+
t.Errorf("Expected panic, got none")
56+
}
57+
}()
58+
59+
PanicWithMessage("this is an expected panic")
60+
}

0 commit comments

Comments
 (0)