Skip to content

30 Go (Golang) Challenges for Junior to Senior Developers [2026]

August 5, 2025 3 min read
Go interview guide by Second Talent

Go (Golang) Challenges for Every Career Stage

In the world of modern software, Go has carved out a crucial space for building fast, reliable, and highly concurrent systems. For developers, proficiency in Go opens doors to roles in cloud computing, DevOps, and backend services.

For employers, finding engineers who can leverage Go’s powerful concurrency model is a top priority.

That’s where focused practice comes in. We’ve designed 30 hands-on Go challenges, split into three distinct levels—10 each for Junior, Mid-Level, and Senior developers.

Whether you’re learning to handle goroutines, mastering interfaces, or preparing for a complex system design interview, these challenges will help you level up your Go expertise.

Jump to Your Level

Junior Developer ️ Mid-Level Developer ⚡️ Senior Developer

Junior Developer Challenges

1. Hello, World!

The traditional first program to verify a working setup.

package main
import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

2. Sum a Slice of Integers

A basic test of loops and slice manipulation.

func SumSlice(numbers []int) int {
    sum := 0
    for _, number := range numbers {
        sum += number
    }
    return sum
}

3. Reverse a String

Demonstrates handling of runes for proper Unicode character reversal.

func ReverseString(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

4. Find the Maximum Value in a Slice

A fundamental algorithm using a simple loop.

func FindMax(numbers []int) (int, error) {
    if len(numbers) == 0 {
        return 0, fmt.Errorf("slice is empty")
    }
    max := numbers[0]
    for _, number := range numbers {
        if number > max {
            max = number
        }
    }
    return max, nil
}

5. Check for Palindrome

Tests string manipulation and comparison logic.

func IsPalindrome(s string) bool {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        if runes[i] != runes[j] {
            return false
        }
    }
    return true
}

6. FizzBuzz

The classic test of basic conditional logic and loops.

func FizzBuzz(n int) {
    for i := 1; i <= n; i++ {
        if i%15 == 0 {
            fmt.Println("FizzBuzz")
        } else if i%5 == 0 {
            fmt.Println("Buzz")
        } else if i%3 == 0 {
            fmt.Println("Fizz")
        } else {
            fmt.Println(i)
        }
    }
}

7. Define and Use a Struct

Introduces custom types and data organization.

type Person struct {
    Name string
    Age  int
}

func NewPerson(name string, age int) Person {
    return Person{Name: name, Age: age}
}

8. Basic Error Handling

Shows how to return and check for errors, a core Go concept.

import "strconv"

func ToInt(s string) (int, error) {
    return strconv.Atoi(s)
}
// Usage: if val, err := ToInt("42"); err == nil { ... }

9. Factorial of a Number

A classic introduction to recursion in Go.

func Factorial(n uint) uint {
    if n <= 1 {
        return 1
    }
    return n * Factorial(n-1)
}

10. Check if a Map Contains a Key

Demonstrates the "comma ok" idiom for map lookups.

func MapContainsKey(m map[string]int, key string) bool {
    _, ok := m[key]
    return ok
}

Mid-Level Developer Challenges

1. Word Frequency Count

Use a map to count the frequency of words in a text.

import "strings"

func WordCount(text string) map[string]int {
    counts := make(map[string]int)
    for _, word := range strings.Fields(text) {
        counts[strings.ToLower(word)]++
    }
    return counts
}

2. Sort a Slice of Structs

Implement the `sort.Interface` to sort custom types.

import "sort"
type Person struct { Name string; Age int }
type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

// Usage: sort.Sort(ByAge(people))

3. Basic Concurrent Data Fetching

Use goroutines and channels to fetch data from multiple sources concurrently.

func FetchData(urls []string) map[string]string {
    ch := make(chan struct{url, body string})
    for _, url := range urls {
        go func(u string) {
            // Assume http.Get(u) returns body
            ch <- struct{url, body string}{u, "body"}
        }(url)
    }
    results := make(map[string]string)
    for range urls {
        res := <-ch
        results[res.url] = res.body
    }
    return results
}

4. JSON Marshalling and Unmarshalling

Convert Go structs to and from JSON.

import "encoding/json"
type User struct { ID int `json:"id"`; Name string `json:"name"` }

// Marshal: json.Marshal(User{...})
// Unmarshal: json.Unmarshal(data, &user)

5. Implement a Simple Web Server

Use the `net/http` package to create a server with a basic handler.

import "net/http"
func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, Gopher!")
}

func StartServer() {
    http.HandleFunc("/", helloHandler)
    http.ListenAndServe(":8080", nil)
}

6. Worker Pool with Channels

Create a pool of goroutines to process jobs from a channel.

func WorkerPool(numWorkers int, jobs <-chan int, results chan<- int) {
    for w := 1; w <= numWorkers; w++ {
        go func(id int, jobs <-chan int, results chan<- int) {
            for j := range jobs {
                // Process job j
                results <- j * 2
            }
        }(w, jobs, results)
    }
}

7. Implement a Set Data Structure

Use a map to create a simple Set type with Add, Has, and Remove methods.

type Set map[interface{}]struct{}

func (s Set) Add(v interface{}) { s[v] = struct{}{} }
func (s Set) Has(v interface{}) bool { _, ok := s[v]; return ok }
func (s Set) Remove(v interface{}) { delete(s, v) }

8. Method with Pointer Receiver

Implement a method that modifies the state of a struct.

type Counter struct { count int }

func (c *Counter) Increment() {
    c.count++
}

9. Check if a Binary Tree is a Valid BST

A recursive algorithm that validates the properties of a Binary Search Tree.

type TreeNode struct { Val int; Left, Right *TreeNode }

func IsValidBST(root *TreeNode) bool {
    return isValid(root, nil, nil)
}

func isValid(node, min, max *TreeNode) bool {
    if node == nil { return true }
    if min != nil && node.Val <= min.Val { return false }
    if max != nil && node.Val >= max.Val { return false }
    return isValid(node.Left, min, node) && isValid(node.Right, node, max)
}

10. Reading and Writing Files

Use the `os` package for basic file I/O operations.

import "os"
// Write
// err := os.WriteFile("test.txt", []byte("Hello"), 0644)
// Read
// data, err := os.ReadFile("test.txt")

Senior Developer Challenges

1. Concurrent Map with Mutex

Build a map that is safe for concurrent read/write operations.

import "sync"
type ConcurrentMap struct {
    mu sync.RWMutex
    items map[string]interface{}
}

func (cm *ConcurrentMap) Set(key string, value interface{}) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    cm.items[key] = value
}

func (cm *ConcurrentMap) Get(key string) (interface{}, bool) {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    value, ok := cm.items[key]
    return value, ok
}

2. Graceful Shutdown of an HTTP Server

Use `context` and channels to allow a server to finish active requests before shutting down.

import "os/signal"
// ...
server := &http.Server{Addr: ":8080"}
go func() {
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatalf("ListenAndServe(): %v", err)
    }
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.Shutdown(ctx)

3. Implement a Rate Limiter

Use a ticker and a channel to limit the rate of events.

func RateLimiter(requests <-chan int, rate time.Duration) <-chan int {
    limit := time.NewTicker(rate)
    out := make(chan int)
    go func() {
        defer close(out)
        for req := range requests {
            <-limit.C
            out <- req
        }
    }()
    return out
}

4. LRU Cache Implementation

A system design problem requiring a map and a doubly linked list for efficiency.

import "container/list"
type LRUCache struct {
    capacity int
    cache    map[int]*list.Element
    ll       *list.List
}
// ... methods for Get and Put would manipulate the map and linked list ...

5. Context-Aware Function

Write a function that accepts a `context.Context` and can be cancelled.

func LongRunningTask(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err() // Task was cancelled
        default:
            // Do some work...
            time.Sleep(100 * time.Millisecond)
        }
    }
}

6. Use Reflection to Inspect a Struct

Use the `reflect` package to dynamically read struct tags and values.

import "reflect"
func InspectStruct(s interface{}) {
    val := reflect.ValueOf(s).Elem()
    for i := 0; i < val.NumField(); i++ {
        field := val.Type().Field(i)
        fmt.Printf("Field: %s, Tag: %s\n", field.Name, field.Tag.Get("json"))
    }
}

7. Build a Simple HTTP Middleware

Create a logging middleware for an `http.Handler`.

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Request: %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}
// Usage: http.Handle("/", LoggingMiddleware(myHandler))

8. Fan-in, Fan-out Concurrency Pattern

A pattern for distributing work and then merging the results.

func FanIn(inputs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)
    output := func(c <-chan int) {
        for n := range c {
            out <- n
        }
        wg.Done()
    }
    wg.Add(len(inputs))
    for _, c := range inputs {
        go output(c)
    }
    go func() { wg.Wait(); close(out) }()
    return out
}

9. Solve the Dining Philosophers Problem

A classic concurrency problem to demonstrate deadlock avoidance.

// The solution involves giving each philosopher two forks (mutexes)
// and ensuring they pick them up in a consistent order (e.g., lowest
// index first) to prevent circular waits and deadlock.
// The implementation requires a struct for philosophers, a slice
// of mutexes for forks, and a main loop for eating/thinking.

10. Implement a Circuit Breaker

A design pattern to prevent repeated calls to a failing external service.

// The implementation would involve a struct that tracks the state
// (Closed, Open, Half-Open), failure counts, and timers. A method
// like Execute(fn) would wrap the external call, checking the state
// before proceeding and updating counts on success or failure.

Tips to Prepare for Go Coding Challenges

  • Think Concurrently: Many Go problems have a concurrent angle. Always consider if goroutines and channels can provide a more efficient or elegant solution.
  • Handle Errors Explicitly: Don't ignore errors. Demonstrating proper `if err != nil` checks is a sign of a mature Go developer. Avoid panicking unless absolutely necessary.
  • Know the Standard Library: Go has a powerful standard library. Get familiar with packages like `net/http`, `encoding/json`, `sync`, `context`, and `sort`.
  • Understand Slices and Maps: Be clear on the difference between a slice's length and capacity, and know how maps work internally (e.g., they are not safe for concurrent use by default).
  • Use `go fmt`: Always format your code. It's a non-negotiable part of the Go ecosystem and shows you care about readability and convention.
  • Embrace Interfaces: Understand how interfaces provide decoupling. Be ready to explain why you might accept an `io.Reader` instead of a concrete `*os.File`.

Conclusion

Go's simplicity and power make it a fantastic language for modern software development. Mastering it comes from practice and a deep understanding of its core philosophies, especially concurrency and error handling.

By tackling these challenges, you'll build the skills and confidence needed to excel in your Go journey. Happy coding!

Hiring developers in Southeast Asia?

Get Cost Guide

How would you like to talk?

WhatsApp us Prefer texting at your own pace? Just hit us up on WhatsApp. We promise no spam and a hassle-free experience.

Loading available times…