Generic Functions — Tasks¶
Table of Contents¶
Introduction¶
20+ exercises arranged from easy to hard. Each task gives: - A clear signature - A short description - A reference solution (collapsed) - Where useful, a sketch of tests
Use these as practice: try the signature first, write tests, then peek at the solution.
Easy Tasks¶
Task 1 — Identity¶
Write a generic function Identity[T any](x T) T that returns its input unchanged.
Task 2 — Pair¶
Write Pair[T any](a, b T) [2]T that returns the two arguments as an array.
Task 3 — First and Last¶
Write First[T any](xs []T) (T, bool) and Last[T any](xs []T) (T, bool).
Solution
Task 4 — Repeat¶
Write Repeat[T any](x T, n int) []T that returns a slice with x repeated n times.
Solution
Task 5 — Contains¶
Write Contains[T comparable](xs []T, target T) bool.
Solution
Task 6 — IndexOf¶
Write IndexOf[T comparable](xs []T, target T) int. Return -1 if not found.
Solution
Task 7 — Reverse¶
Write Reverse[T any](xs []T) that reverses xs in place.
Solution
Medium Tasks¶
Task 8 — Map¶
Write Map[T, U any](xs []T, f func(T) U) []U.
Solution
Task 9 — Filter¶
Write Filter[T any](xs []T, pred func(T) bool) []T.
Solution
Task 10 — Reduce¶
Write Reduce[T, U any](xs []T, init U, f func(U, T) U) U.
Solution
Task 11 — Zip¶
Write Zip[T, U any](as []T, bs []U) []struct{ A T; B U }. Stop at the shorter slice.
Solution
Task 12 — Flatten¶
Write Flatten[T any](xs [][]T) []T.
Solution
Task 13 — GroupBy¶
Write GroupBy[T any, K comparable](xs []T, key func(T) K) map[K][]T.
Solution
Task 14 — Uniq¶
Write Uniq[T comparable](xs []T) []T preserving original order.
Solution
Task 15 — Chunk¶
Write Chunk[T any](xs []T, size int) [][]T. If size <= 0, return nil.
Solution
Task 16 — Partition¶
Write Partition[T any](xs []T, pred func(T) bool) (yes, no []T).
Solution
Hard Tasks¶
Task 17 — Memoize¶
Write a thread-safe Memoize[K comparable, V any](f func(K) V) func(K) V.
Solution
Note: this allows concurrent computation of the same key. To prevent it, use `singleflight` or hold the lock through computation.Task 18 — LRU helper using container/list¶
Sketch a generic LRUCache[K comparable, V any] with Get / Put / Len.
Solution sketch
type entry[K comparable, V any] struct {
key K
value V
}
type LRUCache[K comparable, V any] struct {
capacity int
items map[K]*list.Element
order *list.List
mu sync.Mutex
}
func NewLRU[K comparable, V any](capacity int) *LRUCache[K, V] {
return &LRUCache[K, V]{
capacity: capacity,
items: make(map[K]*list.Element, capacity),
order: list.New(),
}
}
func (c *LRUCache[K, V]) Get(k K) (V, bool) {
c.mu.Lock(); defer c.mu.Unlock()
if el, ok := c.items[k]; ok {
c.order.MoveToFront(el)
return el.Value.(*entry[K, V]).value, true
}
var zero V
return zero, false
}
func (c *LRUCache[K, V]) Put(k K, v V) {
c.mu.Lock(); defer c.mu.Unlock()
if el, ok := c.items[k]; ok {
c.order.MoveToFront(el)
el.Value.(*entry[K, V]).value = v
return
}
el := c.order.PushFront(&entry[K, V]{k, v})
c.items[k] = el
if c.order.Len() > c.capacity {
oldest := c.order.Back()
c.order.Remove(oldest)
delete(c.items, oldest.Value.(*entry[K, V]).key)
}
}
func (c *LRUCache[K, V]) Len() int {
c.mu.Lock(); defer c.mu.Unlock()
return c.order.Len()
}
Task 19 — MapErr¶
Write MapErr[T, U any](xs []T, f func(T) (U, error)) ([]U, error). Return on first error.
Solution
Task 20 — ParallelMap with bounded concurrency¶
Write ParallelMap[T, U any](ctx context.Context, xs []T, n int, f func(context.Context, T) (U, error)) ([]U, error).
Solution
func ParallelMap[T, U any](
ctx context.Context, xs []T, n int,
f func(context.Context, T) (U, error),
) ([]U, error) {
out := make([]U, len(xs))
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(n)
for i, x := range xs {
i, x := i, x
g.Go(func() error {
v, err := f(ctx, x)
if err != nil { return err }
out[i] = v
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return out, nil
}
Task 21 — Compose¶
Write Compose[A, B, C any](f func(A) B, g func(B) C) func(A) C. Then write Compose3[A, B, C, D any](...).
Solution
Task 22 — Generic event bus¶
Write a simple EventBus[T any] with Subscribe(h func(T)) (cancel func()) and Publish(e T). Make it concurrency-safe.
Solution sketch
type EventBus[T any] struct {
mu sync.RWMutex
subscribers map[uint64]func(T)
next uint64
}
func NewEventBus[T any]() *EventBus[T] {
return &EventBus[T]{subscribers: make(map[uint64]func(T))}
}
func (b *EventBus[T]) Subscribe(h func(T)) func() {
b.mu.Lock()
id := b.next
b.next++
b.subscribers[id] = h
b.mu.Unlock()
return func() {
b.mu.Lock()
delete(b.subscribers, id)
b.mu.Unlock()
}
}
func (b *EventBus[T]) Publish(e T) {
b.mu.RLock()
handlers := make([]func(T), 0, len(b.subscribers))
for _, h := range b.subscribers { handlers = append(handlers, h) }
b.mu.RUnlock()
for _, h := range handlers { h(e) }
}
Bonus Tasks¶
Task 23 — Window¶
Write Window[T any](xs []T, size int) [][]T that returns all sliding windows of length size.
Solution
Task 24 — Distinct by key¶
Write DistinctBy[T any, K comparable](xs []T, key func(T) K) []T.
Solution
Task 25 — Coalesce¶
Write Coalesce[T comparable](xs ...T) T that returns the first non-zero argument.
Solution
Task 26 — Async pipeline¶
Write a generic Stage[T, U any] chain: each stage reads from a channel of T and produces a channel of U.
Solution sketch
func Stage[T, U any](in <-chan T, f func(T) U) <-chan U {
out := make(chan U)
go func() {
defer close(out)
for x := range in {
out <- f(x)
}
}()
return out
}
// Usage:
src := make(chan int)
go func() {
defer close(src)
for i := 0; i < 5; i++ { src <- i }
}()
strs := Stage(src, strconv.Itoa)
upper := Stage(strs, strings.ToUpper)
for s := range upper { fmt.Println(s) }
Test Templates¶
A small example for your tests:
func TestMap(t *testing.T) {
cases := []struct {
name string
in []int
want []string
}{
{"empty", nil, []string{}},
{"one", []int{1}, []string{"1"}},
{"many", []int{1, 2, 3}, []string{"1", "2", "3"}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := Map(c.in, strconv.Itoa)
if !reflect.DeepEqual(got, c.want) {
t.Errorf("Map(%v) = %v; want %v", c.in, got, c.want)
}
})
}
}
func TestUniq(t *testing.T) {
in := []int{1, 2, 2, 3, 1, 4}
want := []int{1, 2, 3, 4}
if got := Uniq(in); !reflect.DeepEqual(got, want) {
t.Errorf("Uniq = %v; want %v", got, want)
}
}
For benchmarking:
func BenchmarkMap(b *testing.B) {
xs := make([]int, 1000)
for i := range xs { xs[i] = i }
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Map(xs, func(n int) int { return n * 2 })
}
}
Summary¶
These exercises build muscle memory for the patterns you'll write daily: slice utilities, key-based grouping, parallel transforms, simple caches. Solving them top-to-bottom gives you working drafts of a generic helper library; solving the bonus tasks pushes you closer to mid-level fluency.