Skip to content

Go Basics Practice (Interactive)

Go — Basics Practice

8 auto-graded practice problems covering core Go concepts. Select an answer, submit, and review the explanation.

Worked Examples

Example 1: Basic Types and Type Inference

package main

import "fmt"

func main() \{
    // Go infers int from the literal
    var x = 42
    fmt.Printf("Type of x: %T, Value: %v\n", x, x)

    // Explicit type declaration
    var y float64 = 3.14
    fmt.Printf("Type of y: %T, Value: %v\n", y, y)

    // Short variable declaration (most common)
    z := "hello"
    fmt.Printf("Type of z: %T, Value: %v\n", z, z)

    // Multiple declarations
    a, b, c := 1, 2.0, "three"
    fmt.Printf("a=%T, b=%T, c=%T\n", a, b, c)
\}

Output:

Type of x: int, Value: 42
Type of y: float64, Value: 3.14
Type of z: string, Value: hello
a=int, b=float64, c=string

Key insight: Go uses type inference to determine types from literals. 42 is an int, 3.14 is a float64, and "hello" is a string. You rarely need explicit type annotations.


Example 2: Variadic Functions and Error Handling

package main

import (
    "errors"
    "fmt"
)

// Variadic function that accepts any number of integers
func sum(nums ...int) int \{
    total := 0
    for _, n := range nums \{
        total += n
    \}
    return total
\}

// Function returning multiple values with error
func divide(a, b float64) (float64, error) \{
    if b == 0 \{
        return 0, errors.New("division by zero")
    \}
    return a / b, nil
\}

func main() \{
    // Calling variadic function
    result := sum(1, 2, 3, 4, 5)
    fmt.Printf("Sum: %d\n", result)

    // Error handling pattern
    if quotient, err := divide(10, 3); err != nil \{
        fmt.Printf("Error: %v\n", err)
    \} else \{
        fmt.Printf("10 / 3 = %.4f\n", quotient)
    \}

    // Error case
    if _, err := divide(10, 0); err != nil \{
        fmt.Printf("Error: %v\n", err)
    \}
\}

Output:

Sum: 15
10 / 3 = 3.3333
Error: division by zero

Key insight: Go functions return multiple values, with errors as the last return value. The if err != nil pattern is idiomatic Go for error handling.


Example 3: Interfaces and Structural Typing

package main

import "fmt"

// Define an interface
type Stringer interface \{
    String() string
\}

// Define a type
type MyInt int

// Implement the interface (no explicit "implements" keyword)
func (m MyInt) String() string \{
    return fmt.Sprintf("MyInt(%d)", int(m))
\}

// Function accepting an interface
func printTwice(s Stringer) \{
    fmt.Println(s.String())
    fmt.Println(s.String())
\}

func main() \{
    x := MyInt(42)
    printTwice(x)

    // MyInt satisfies Stringer implicitly
    var s Stringer = x
    fmt.Printf("Interface value: %s\n", s.String())
\}

Output:

MyInt(42)
MyInt(42)
Interface value: MyInt(42)

Key insight: Go uses structural typing — any type that implements all methods of an interface automatically satisfies it. No explicit declaration needed.


Example 4: Goroutines and Channels

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) \{
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    // Simulate work
    fmt.Printf("Worker %d done\n", id)
\}

func main() \{
    var wg sync.WaitGroup

    // Launch 3 goroutines
    for i := 1; i <= 3; i++ \{
        wg.Add(1)
        go worker(i, &wg)
    \}

    // Wait for all goroutines to complete
    wg.Wait()
    fmt.Println("All workers finished")
\}

Output (order may vary):

Worker 1 starting
Worker 1 done
Worker 2 starting
Worker 2 done
Worker 3 starting
Worker 3 done
All workers finished

Key insight: Goroutines are lightweight threads managed by the Go runtime. Use sync.WaitGroup to wait for multiple goroutines to complete.


Types


Functions


Control Flow


Error Handling

Intuition

Go basics establish the foundation for all Go programming: Types, functions, control flow, and error handling are the building blocks. Go’s explicit error handling forces you to think about failure modes at every step.

Why it matters: Mastering Go basics enables you to write efficient, readable Go code that takes advantage of the language’s simplicity and performance.

The key insight: Go’s error handling (returning error values) is verbose but explicit — you always know what can fail and how failures are handled.

Common Mistakes

Ignoring error returns: Go functions that can fail return error values. Forgetting to check if err != nil means silent failures that are hard to debug. Always handle errors explicitly — the linter will flag unchecked errors.

Confusing value receivers with pointer receivers: Methods on value receivers operate on copies of the struct. Methods on pointer receivers modify the original. If your method needs to modify state, use a pointer receiver. Mixing receiver types inconsistently causes subtle bugs.

Overusing init() functions: Package-level init() runs automatically and implicitly. This makes control flow hard to follow and testing difficult. Prefer explicit initialization through constructors or dependency injection.

Cross-References