Showing posts with label go. Show all posts
Showing posts with label go. Show all posts

GO 61 to 75

Chapter 61 — Building CLI Applications

Question

Given below is a code snippet that:

  • Reads command-line arguments.

  • Checks whether an argument was supplied.

  • Converts a string argument into an integer.

  • Handles invalid input.

  • Demonstrates a practical Go CLI pattern.

What should be the output of the following code?

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("usage: app <number>")
		return
	}

	number, err := strconv.Atoi(os.Args[1])
	if err != nil {
		fmt.Println("invalid number")
		return
	}

	fmt.Println("double:", number*2)
}

Assume the program is run as:

go run main.go 21

Answer

double: 42

Step-by-step explanation

  1. os.Args contains the command-line arguments.

  2. os.Args[0] is normally the program name.

  3. os.Args[1] is therefore "21".

  4. strconv.Atoi converts "21" from a string to the integer 21.

  5. err is nil, meaning conversion succeeded.

  6. 21 * 2 produces 42.

How to read the important code

number, err := strconv.Atoi(os.Args[1])

"number comma err colon equals strconv dot Atoi, open parenthesis, os dot Args square bracket one, close parenthesis."

Key takeaway

A Go CLI commonly reads arguments, validates them, converts them into useful types, and handles errors explicitly.


Chapter 62 — Working with External APIs

Question

Given below is a code snippet that:

  • Creates an HTTP client.

  • Sends a GET request to an external API.

  • Checks the HTTP status.

  • Decodes JSON into a Go struct.

  • Separates API communication from the data structure representing the response.

What should be the output of the following code?

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
)

type APIUser struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func fetchUser(client *http.Client, url string) (APIUser, error) {
	resp, err := client.Get(url)
	if err != nil {
		return APIUser{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return APIUser{}, fmt.Errorf("unexpected status: %d", resp.StatusCode)
	}

	var user APIUser

	if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
		return APIUser{}, err
	}

	return user, nil
}

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `{"id":7,"name":"Alice"}`)
	}))
	defer server.Close()

	user, err := fetchUser(server.Client(), server.URL)

	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(user.ID)
	fmt.Println(user.Name)
}

Answer

7
Alice

Step-by-step explanation

  1. httptest.NewServer creates a temporary HTTP server.

  2. The server returns JSON containing an ID and name.

  3. fetchUser sends a GET request.

  4. defer resp.Body.Close() ensures the response body is closed after the function finishes.

  5. The status code is checked.

  6. json.NewDecoder reads the JSON response directly into APIUser.

  7. The function returns the populated struct.

  8. The program prints 7 and Alice.

How to read the important code

if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {

"if err colon equals json dot NewDecoder, open parenthesis, resp dot Body, close parenthesis, dot Decode, open parenthesis, ampersand user, close parenthesis, semicolon, err is not equal to nil."

Key takeaway

When consuming an external API, handle transport errors, HTTP status errors, response cleanup, and JSON decoding separately.


Chapter 63 — Caching & Background Jobs

Question

Given below is a code snippet that:

  • Stores expensive results in an in-memory cache.

  • Returns the cached value on subsequent requests.

  • Uses a background goroutine to refresh data.

  • Demonstrates the basic idea behind caching and background work.

What should be the output of the following code?

package main

import (
	"fmt"
	"sync"
	"time"
)

type Cache struct {
	mu   sync.Mutex
	data map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()

	value, ok := c.data[key]
	return value, ok
}

func (c *Cache) Set(key, value string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.data[key] = value
}

func main() {
	cache := &Cache{data: make(map[string]string)}

	go func() {
		time.Sleep(20 * time.Millisecond)
		cache.Set("weather", "sunny")
		fmt.Println("background job: cache refreshed")
	}()

	cache.Set("weather", "cloudy")
	fmt.Println("initial:", cacheValue(cache, "weather"))

	time.Sleep(50 * time.Millisecond)

	fmt.Println("after job:", cacheValue(cache, "weather"))
}

func cacheValue(cache *Cache, key string) string {
	value, ok := cache.Get(key)

	if !ok {
		return "missing"
	}

	return value
}

Answer

initial: cloudy
background job: cache refreshed
after job: sunny

Step-by-step explanation

  1. The cache starts empty.

  2. A background goroutine starts.

  3. The main goroutine stores "cloudy".

  4. The first lookup therefore returns "cloudy".

  5. The background job waits 20 milliseconds.

  6. It then replaces the value with "sunny".

  7. The main goroutine waits 50 milliseconds, giving the background job enough time to run.

  8. The second lookup therefore returns "sunny".

  9. sync.Mutex prevents simultaneous access from corrupting the map.

How to read the important code

mu sync.Mutex

"mu, type sync dot Mutex."

A mutex is a lock that allows only one goroutine at a time to enter protected code.

Key takeaway

Caching avoids repeated expensive work, while background jobs move work away from the main request path.


Chapter 64 — WebSockets & Real-Time Applications

Question

Given below is a code snippet that:

  • Demonstrates the core idea of a real-time connection.

  • Uses a simulated message channel to represent incoming messages.

  • Processes messages continuously.

  • Shows the pattern used by real-time applications.

What should be the output of the following code?

package main

import "fmt"

func main() {
	messages := make(chan string)

	go func() {
		messages <- "connected"
		messages <- "new message"
		messages <- "disconnected"
		close(messages)
	}()

	for message := range messages {
		fmt.Println(message)
	}
}

Answer

connected
new message
disconnected

Step-by-step explanation

  1. messages is an unbuffered channel of strings.

  2. The goroutine sends "connected".

  3. The main goroutine receives it through the range.

  4. The process repeats for the next two messages.

  5. close(messages) tells receivers that no more messages will arrive.

  6. The range loop automatically stops after the channel is closed and emptied.

A real WebSocket application adds a network connection around this same general idea: messages can arrive continuously rather than through ordinary request/response HTTP calls.

How to read the important code

for message := range messages {

"for message colon equals range messages, open curly brace."

Key takeaway

Real-time systems continuously receive and process events instead of waiting for a new HTTP request for every message.


Chapter 65 — Dockerizing Go Applications

Question

Given below is a code snippet that:

  • Shows a minimal Go application suitable for containerization.

  • Demonstrates that the application listens on a configurable port.

  • Uses an environment variable.

  • Illustrates the application behavior expected inside a Docker container.

What should be the output of the following code?

package main

import (
	"fmt"
	"os"
)

func main() {
	port := os.Getenv("PORT")

	if port == "" {
		port = "8080"
	}

	fmt.Println("application listening on port", port)
}

Assume the container is started with:

PORT=9000

Answer

application listening on port 9000

Step-by-step explanation

  1. Containers should generally receive environment-specific configuration from outside the application.

  2. os.Getenv("PORT") reads the PORT environment variable.

  3. The container supplies 9000.

  4. Therefore the application uses port 9000.

  5. If PORT were absent, the code would use 8080.

A typical production Docker setup would compile the Go program into a binary and place that binary into a small runtime image.

How to read the important code

port := os.Getenv("PORT")

"port colon equals os dot Getenv, open parenthesis, quote PORT quote, close parenthesis."

Key takeaway

A container should package the application consistently while environment variables provide environment-specific configuration.


Chapter 66 — Go in CI/CD

Question

Given below is a code snippet that:

  • Contains a function with a testable result.

  • Demonstrates the kind of code a CI pipeline can automatically test.

  • Shows how a failing test would indicate a broken build.

  • Represents the relationship between source code and automated quality checks.

What should be the output of the following code?

package main

import "fmt"

func Add(a, b int) int {
	return a + b
}

func main() {
	fmt.Println(Add(10, 20))
}

Assume the CI pipeline performs:

go test ./...
go build ./...

and the repository contains a test that checks:

Add(10, 20) == 30

Answer

CI result:
tests passed
build passed

Program output:
30

Step-by-step explanation

  1. Add returns the sum of two integers.

  2. The automated test checks whether Add(10, 20) equals 30.

  3. The test passes.

  4. go build ./... verifies that the project can be compiled.

  5. Therefore the CI pipeline can mark this revision as successful.

  6. CI means Continuous Integration: automatically checking changes as they are submitted.

  7. CD commonly means Continuous Delivery/Deployment: automatically preparing or deploying validated software.

How to read the important code

go test ./...

"go test dot slash dot dot dot."

./... means the current module and packages beneath it.

Key takeaway

CI/CD turns testing, building, and deployment checks into repeatable automated steps instead of relying entirely on manual checking.


Chapter 67 — Git & Go Development Workflow

Question

Given below is a code snippet that:

  • Represents a small change made during normal development.

  • Demonstrates a function that can be tested independently.

  • Shows the kind of change a developer would commit and push through Git.

  • Connects source-code changes with testing and collaboration.

What should be the output of the following code?

package main

import "fmt"

func IsAdult(age int) bool {
	return age >= 18
}

func main() {
	ages := []int{17, 18, 25}

	for _, age := range ages {
		fmt.Println(age, IsAdult(age))
	}
}

Answer

17 false
18 true
25 true

Step-by-step explanation

  1. ages contains three test values.

  2. The for loop processes each age.

  3. IsAdult returns false for 17.

  4. 18 >= 18 is true.

  5. 25 >= 18 is also true.

  6. In a real workflow, you might modify this function, run tests, inspect the change with Git, commit it, and push it for review.

A common workflow is:

edit → test → git diff → commit → push → CI → review → merge

How to read the important code

for _, age := range ages {

"for blank identifier comma age colon equals range ages, open curly brace."

The _ is the blank identifier. It means "I intentionally don't need this value."

Key takeaway

Professional Go development is not just writing code; it is writing, testing, reviewing, versioning, and integrating code safely.


Chapter 68 — Common Go Interview Questions

Question

Given below is a code snippet that:

  • Tests your understanding of slices.

  • Demonstrates that two slice variables can refer to the same underlying array.

  • Shows how mutation through one slice can affect another.

  • Tests an important Go interview concept.

What should be the output of the following code?

package main

import "fmt"

func main() {
	numbers := []int{10, 20, 30}

	a := numbers
	b := numbers[:2]

	b[0] = 99

	fmt.Println(numbers)
	fmt.Println(a)
	fmt.Println(b)
}

Answer

[99 20 30]
[99 20 30]
[99 20]

Step-by-step explanation

  1. numbers is a slice containing three elements.

  2. a := numbers creates another slice referring to the same underlying data.

  3. b := numbers[:2] creates a slice containing the first two elements.

  4. These slices share the same underlying array.

  5. b[0] = 99 changes the first element of that shared array.

  6. Therefore numbers[0] and a[0] also become 99.

  7. b contains only the first two elements.

How to read the important code

b := numbers[:2]

"b colon equals numbers, colon two, square bracket."

It means: create a slice from the beginning of numbers up to, but not including, index 2.

Key takeaway

A slice is a descriptor of an underlying array, so copying a slice does not necessarily copy its elements.


Chapter 69 — Go Coding Interview Problems

Question

Given below is a code snippet that:

  • Uses a map to detect duplicate values.

  • Demonstrates a common interview algorithm.

  • Processes the input only once.

  • Shows how a Go map can provide fast membership checking.

What should be the output of the following code?

package main

import "fmt"

func firstDuplicate(numbers []int) (int, bool) {
	seen := make(map[int]bool)

	for _, number := range numbers {
		if seen[number] {
			return number, true
		}

		seen[number] = true
	}

	return 0, false
}

func main() {
	numbers := []int{4, 7, 2, 7, 9}

	number, found := firstDuplicate(numbers)

	fmt.Println(number)
	fmt.Println(found)
}

Answer

7
true

Step-by-step explanation

  1. seen starts as an empty map.

  2. 4 is not present, so it is added.

  3. 7 is not present, so it is added.

  4. 2 is not present, so it is added.

  5. The next 7 is already in the map.

  6. The function immediately returns 7 and true.

  7. It does not need to inspect 9.

How to read the important code

if seen[number] {

"if seen square bracket number square bracket, open curly brace."

The map lookup returns the stored boolean value.

Key takeaway

A map can turn repeated membership checks into an efficient one-pass algorithm.


Chapter 70 — Go System Design Fundamentals

Question

Given below is a code snippet that:

  • Separates an HTTP handler from business logic.

  • Uses an interface between layers.

  • Demonstrates dependency injection.

  • Represents a simplified production service architecture.

  • Shows how system components can have clearly defined responsibilities.

What should be the output of the following code?

package main

import "fmt"

type UserStore interface {
	FindName(id int) string
}

type Database struct{}

func (Database) FindName(id int) string {
	return "Alice"
}

type UserService struct {
	store UserStore
}

func (s UserService) GetUserName(id int) string {
	return s.store.FindName(id)
}

type Handler struct {
	service UserService
}

func (h Handler) Serve(id int) {
	name := h.service.GetUserName(id)
	fmt.Println("HTTP 200:", name)
}

func main() {
	db := Database{}
	service := UserService{store: db}
	handler := Handler{service: service}

	handler.Serve(42)
}

Answer

HTTP 200: Alice

Step-by-step explanation

  1. Database implements UserStore.

  2. UserService depends on the UserStore interface rather than directly depending on a concrete database.

  3. Handler depends on UserService.

  4. The request enters the handler.

  5. The handler asks the service for the user.

  6. The service asks the store.

  7. The database returns "Alice".

  8. The handler produces the response.

The architectural flow is:

HTTP Handler
     ↓
Service / Business Logic
     ↓
Repository / Data Access
     ↓
Database

How to read the important code

type UserStore interface {
	FindName(id int) string
}

"type UserStore interface, open curly brace, FindName open parenthesis id int close parenthesis string, close curly brace."

Key takeaway

Good system design separates responsibilities and makes dependencies replaceable and testable.


Chapter 71 — Production Debugging Scenarios

Question

Given below is a code snippet that:

  • Demonstrates a common production bug involving shared state.

  • Uses a mutex to protect a shared counter.

  • Runs multiple goroutines concurrently.

  • Shows the correct result after synchronization.

What should be the output of the following code?

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	counter := 0

	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()

			mu.Lock()
			counter++
			mu.Unlock()
		}()
	}

	wg.Wait()

	fmt.Println(counter)
}

Answer

1000

Step-by-step explanation

  1. counter starts at 0.

  2. The loop launches 1,000 goroutines.

  3. Each goroutine must increment the same variable.

  4. counter++ is a read-modify-write operation.

  5. Without synchronization, multiple goroutines could interfere with one another, causing a race condition.

  6. mu.Lock() allows only one goroutine at a time to modify the counter.

  7. mu.Unlock() releases the lock.

  8. WaitGroup ensures the main goroutine waits for all 1,000 goroutines.

  9. Therefore the final value is 1000.

How to read the important code

mu.Lock()
counter++
mu.Unlock()

"mu dot Lock, open parenthesis, close parenthesis; counter plus plus; mu dot Unlock, open parenthesis, close parenthesis."

Key takeaway

When multiple goroutines access shared mutable data, synchronization is often necessary to prevent race conditions.


Chapter 72 — Complete Job-Ready Go Project

Question

Given below is a code snippet that:

  • Combines HTTP handling, JSON, a service layer, an in-memory repository, error handling, and HTTP status codes.

  • Represents a miniature backend application.

  • Demonstrates how the concepts learned throughout the course fit together.

What should be the output of the following code?

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

type UserRepository struct {
	users map[int]User
}

func (r UserRepository) Find(id int) (User, error) {
	user, ok := r.users[id]
	if !ok {
		return User{}, fmt.Errorf("user not found")
	}

	return user, nil
}

type UserService struct {
	repo UserRepository
}

func (s UserService) GetUser(id int) (User, error) {
	return s.repo.Find(id)
}

func NewHandler(service UserService) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, err := service.GetUser(1)

		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(user)
	})
}

func main() {
	repo := UserRepository{
		users: map[int]User{
			1: {ID: 1, Name: "Alice"},
		},
	}

	service := UserService{repo: repo}
	handler := NewHandler(service)

	req := httptest.NewRequest(http.MethodGet, "/users/1", nil)
	rec := httptest.NewRecorder()

	handler.ServeHTTP(rec, req)

	fmt.Println(rec.Code)
	fmt.Print(rec.Body.String())
}

Answer

200
{"id":1,"name":"Alice"}

Step-by-step explanation

  1. The repository contains Alice.

  2. The service depends on the repository.

  3. NewHandler receives the service.

  4. A fake HTTP request is created.

  5. The handler asks the service for user 1.

  6. The repository finds Alice.

  7. The handler sets the response content type.

  8. json.NewEncoder converts Alice into JSON.

  9. The default successful HTTP status is 200.

  10. The response body contains Alice's JSON representation.

How to read the important code

handler := NewHandler(service)

"handler colon equals NewHandler, open parenthesis, service, close parenthesis."

This creates the HTTP handler while injecting the service it needs.

Key takeaway

A job-ready Go project combines small understandable components into a complete, testable application rather than putting everything in one giant function.


Chapter 73 — Advanced Go Patterns

Question

Given below is a code snippet that:

  • Demonstrates a generic function.

  • Works with different numeric types.

  • Uses a type constraint.

  • Shows how generics can eliminate duplicated functions while retaining compile-time type checking.

What should be the output of the following code?

package main

import "fmt"

type Number interface {
	int | float64
}

func Sum[T Number](values []T) T {
	var total T

	for _, value := range values {
		total += value
	}

	return total
}

func main() {
	fmt.Println(Sum([]int{10, 20, 30}))
	fmt.Println(Sum([]float64{1.5, 2.5}))
}

Answer

60
4

Step-by-step explanation

  1. Number is a type constraint.

  2. It says that T can be either int or float64.

  3. Sum[T Number] is a generic function.

  4. T represents a type chosen when the function is used.

  5. The first call uses int.

  6. The second call uses float64.

  7. The same function therefore works with both types.

  8. The first sum is 60.

  9. The second sum is 4.

How to read the important code

func Sum[T Number](values []T) T

"func Sum, square bracket T Number, square bracket, open parenthesis, values square bracket T, close parenthesis, T."

The T is a type parameter. It allows the function to operate on different types.

Key takeaway

Generics let you write reusable, type-safe code that works with multiple types.


Chapter 74 — Go Performance & Scalability

Question

Given below is a code snippet that:

  • Demonstrates the effect of preallocating slice capacity.

  • Uses make to allocate a slice with known capacity.

  • Avoids repeated growth of the underlying array in this example.

  • Introduces an important performance optimization without changing the result.

What should be the output of the following code?

package main

import "fmt"

func buildNumbers() []int {
	numbers := make([]int, 0, 5)

	for i := 1; i <= 5; i++ {
		numbers = append(numbers, i)
	}

	return numbers
}

func main() {
	numbers := buildNumbers()

	fmt.Println(numbers)
	fmt.Println(len(numbers))
	fmt.Println(cap(numbers))
}

Answer

[1 2 3 4 5]
5
5

Step-by-step explanation

  1. make([]int, 0, 5) creates an integer slice with length 0 and capacity 5.

  2. Length means the number of elements currently in the slice.

  3. Capacity means how many elements the underlying storage can hold before it needs to grow.

  4. Five numbers are appended.

  5. The final length becomes 5.

  6. Because the initial capacity was already 5, the slice has enough capacity for all five elements.

  7. Therefore the final capacity is 5.

Preallocation can improve performance when you know approximately how many elements you will need because it can reduce unnecessary allocations and copying.

How to read the important code

numbers := make([]int, 0, 5)

"numbers colon equals make, open parenthesis, square bracket int, zero, comma, five, close parenthesis."

This creates a slice of int with length 0 and capacity 5.

Key takeaway

Performance optimization in Go often starts with reducing unnecessary allocations, copying, blocking, and work—not with clever code.


Chapter 75 — Final Job-Level Go Mastery & Interview Preparation

Question

Given below is a code snippet that:

  • Combines interfaces, dependency injection, error handling, concurrency, synchronization, and HTTP-style application logic.

  • Represents the type of integrated thinking expected from a professional Go developer.

  • Uses a worker goroutine to process a job.

  • Demonstrates the complete flow from input to result.

What should be the output of the following code?

package main

import (
	"errors"
	"fmt"
	"sync"
)

type Repository interface {
	Find(id int) (string, error)
}

type MemoryRepository struct {
	data map[int]string
}

func (r MemoryRepository) Find(id int) (string, error) {
	name, ok := r.data[id]

	if !ok {
		return "", errors.New("user not found")
	}

	return name, nil
}

type Service struct {
	repo Repository
}

func (s Service) Process(id int) (string, error) {
	name, err := s.repo.Find(id)

	if err != nil {
		return "", err
	}

	return "Welcome, " + name, nil
}

func main() {
	repo := MemoryRepository{
		data: map[int]string{
			1: "Alice",
		},
	}

	service := Service{repo: repo}

	jobs := make(chan int)
	results := make(chan string)

	var wg sync.WaitGroup

	wg.Add(1)

	go func() {
		defer wg.Done()

		for id := range jobs {
			message, err := service.Process(id)

			if err != nil {
				results <- "error: " + err.Error()
				continue
			}

			results <- message
		}
	}()

	go func() {
		jobs <- 1
		close(jobs)
		wg.Wait()
		close(results)
	}()

	for result := range results {
		fmt.Println(result)
	}
}

Answer

Welcome, Alice

Step-by-step explanation

  1. MemoryRepository implements the Repository interface.

  2. Service depends on the interface rather than directly depending on the concrete repository.

  3. A jobs channel carries user IDs to the worker.

  4. A results channel carries processed results back.

  5. A worker goroutine receives job 1.

  6. The service asks the repository for user 1.

  7. The repository returns "Alice".

  8. The service creates "Welcome, Alice".

  9. The worker sends that result through the results channel.

  10. The main goroutine receives it and prints it.

  11. The job channel is closed after the job is sent.

  12. The worker finishes.

  13. The WaitGroup ensures the worker has finished before results is closed.

  14. The results loop then finishes safely.

How to read the important code

for result := range results {

"for result colon equals range results, open curly brace."

This means:

Keep receiving results from the channel until the channel is closed.

Key takeaway

Job-ready Go means being able to combine fundamentals—interfaces, errors, concurrency, synchronization, APIs, testing, databases, and clean architecture—into reliable software.

GO 51 - 60

Chapter 51 — SQL Transactions & Connection Management

Question

Given below is a code snippet that:

  • Demonstrates the basic structure of a database transaction.

  • Demonstrates Begin, Commit, and Rollback.

  • Demonstrates why an error should cause a transaction to be rolled back.

  • Demonstrates the intended all-or-nothing nature of a transaction.

GO 41 to 50

Chapter 41 — Generics

Question

Given below is a code snippet that:

  • Demonstrates a generic function.

  • Uses one function with different data types.

  • Demonstrates a type parameter and a type constraint.

  • Demonstrates how Go infers the type automatically.

GO 31 to 40

Chapter 31 — REST API Development

Question

Given below is a code snippet that:

  • Creates an HTTP server.

  • Defines a REST-style endpoint for retrieving a user.

  • Returns JSON with an HTTP status code.

  • Demonstrates how a handler processes a request.

  • Shows the difference between a successful response and a missing resource.

What should be the output of the following code?

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func getUser(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")

	if id != "1" {
		http.Error(w, `{"error":"user not found"}`, http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)

	json.NewEncoder(w).Encode(User{ID: 1, Name: "Go Developer"})
}

func main() {
	server := http.HandlerFunc(getUser)

	for _, id := range []string{"1", "2"} {
		req := httptest.NewRequest(http.MethodGet, "/users?id="+id, nil)
		rec := httptest.NewRecorder()

		server.ServeHTTP(rec, req)

		fmt.Println(rec.Code)
		fmt.Print(rec.Body.String())
	}
}

Answer

200
{"id":1,"name":"Go Developer"}
404
{"error":"user not found"}

Step-by-step explanation

  1. httptest.NewRequest creates a test HTTP request. It does not send a real network request.

  2. httptest.NewRecorder captures the response so we can inspect it.

  3. server.ServeHTTP(rec, req) runs the handler.

  4. For id=1, the handler writes status 200 and encodes the user as JSON.

  5. json.NewEncoder(w).Encode(...) converts the struct into JSON and adds a newline.

  6. For id=2, the condition is true, so http.Error writes the error response and status 404.

  7. return stops the handler immediately.

How to read the important code

server.ServeHTTP(rec, req)

"server dot ServeHTTP, open parenthesis, rec comma req, close parenthesis."

This means: run the HTTP handler using this request and record the response.

json.NewEncoder(w).Encode(user)

"json dot NewEncoder, open parenthesis, w, close parenthesis, dot Encode, open parenthesis, user, close parenthesis."

This means: create a JSON encoder that writes to the response, then encode the user.

Key takeaway

A REST API handler receives a request, decides what to return, and writes a response with a status code and data.


Chapter 32 — Middleware

Question

Given below is a code snippet that:

  • Creates middleware that runs before and after a handler.

  • Demonstrates how middleware wraps another handler.

  • Shows execution order.

  • Uses a request ID to identify a request.

  • Demonstrates how middleware can modify a response.

What should be the output of the following code?

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
)

func logging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("before handler")

		next.ServeHTTP(w, r)

		fmt.Println("after handler")
	})
}

func addRequestID(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Request-ID", "abc123")
		next.ServeHTTP(w, r)
	})
}

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("inside handler")
		fmt.Fprintln(w, "Hello")
	})

	handler = logging(addRequestID(handler))

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()

	handler.ServeHTTP(rec, req)

	fmt.Println("response:", rec.Body.String())
	fmt.Println("request ID:", rec.Header().Get("X-Request-ID"))
}

Answer

before handler
inside handler
after handler
response: Hello

request ID: abc123

Step-by-step explanation

  1. addRequestID(handler) wraps the original handler.

  2. logging(...) wraps that result.

  3. The outer logging middleware runs first and prints before handler.

  4. It calls the next middleware.

  5. The request ID middleware sets the response header and calls the original handler.

  6. The original handler prints inside handler and writes Hello.

  7. Control returns to the logging middleware, which prints after handler.

  8. rec.Body.String() contains "Hello\n" because fmt.Fprintln adds a newline.

How to read the important code

func logging(next http.Handler) http.Handler

"func logging, open parenthesis, next http dot Handler, close parenthesis, http dot Handler."

This means: logging receives a handler and returns another handler.

handler = logging(addRequestID(handler))

"handler equals logging, open parenthesis, addRequestID, open parenthesis, handler, close parenthesis, close parenthesis."

This means: wrap the handler with request-ID middleware, then wrap that with logging middleware.

Key takeaway

Middleware is a handler that adds behavior before or after another handler.


Chapter 33 — Routing & Request Handling

Question

Given below is a code snippet that:

  • Routes different HTTP methods to different handlers.

  • Demonstrates path parameters using Go's standard library router.

  • Returns 405 Method Not Allowed for an unsupported method.

  • Shows how a handler reads a path parameter.

  • Demonstrates how routing selects the correct handler.

What should be the output of the following code?

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
)

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "GET user:", r.PathValue("id"))
	})

	mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "POST user")
	})

	for _, test := range []struct {
		method string
		path   string
	}{
		{http.MethodGet, "/users/42"},
		{http.MethodPost, "/users"},
		{http.MethodDelete, "/users/42"},
	} {
		req := httptest.NewRequest(test.method, test.path, nil)
		rec := httptest.NewRecorder()

		mux.ServeHTTP(rec, req)

		fmt.Println(test.method, test.path, rec.Code)
		fmt.Print(rec.Body.String())
	}
}

Answer

GET /users/42 200
GET user: 42
POST /users 200
POST user
DELETE /users/42 405
Method Not Allowed

Step-by-step explanation

  1. http.NewServeMux() creates a router.

  2. "GET /users/{id}" matches GET requests whose path begins with /users/.

  3. {id} is a path wildcard.

  4. r.PathValue("id") retrieves the value captured by that wildcard.

  5. "POST /users" matches only POST requests to /users.

  6. The DELETE request does not match an allowed method for that route, so the router returns 405.

  7. rec.Code records the HTTP status code.

How to read the important code

mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {

"mux dot HandleFunc, open parenthesis, double quote GET slash users slash curly brace id curly brace double quote, comma, func..."

This means: register an anonymous function to handle GET requests matching /users/{id}.

r.PathValue("id")

"r dot PathValue, open parenthesis, double quote id double quote, close parenthesis."

This means: retrieve the value captured by the {id} path parameter.

Key takeaway

A router selects a handler based on the HTTP method and URL path.


Chapter 34 — Goroutines

Question

Given below is a code snippet that:

  • Starts a goroutine.

  • Demonstrates that a goroutine runs concurrently with the main function.

  • Uses a channel to wait for completion.

  • Shows how execution order can differ from source-code order.

  • Demonstrates how a channel can signal that work is finished.

What should be the output of the following code?

package main

import "fmt"

func main() {
	done := make(chan bool)

	go func() {
		fmt.Println("worker: started")
		fmt.Println("worker: finished")
		done <- true
	}()

	fmt.Println("main: waiting")
	<-done
	fmt.Println("main: finished")
}

Answer

main: waiting
worker: started
worker: finished
main: finished

Step-by-step explanation

  1. make(chan bool) creates a channel that carries boolean values.

  2. go func() { ... }() starts the anonymous function in a goroutine.

  3. The main function prints main: waiting.

  4. The worker prints its two messages.

  5. done <- true sends true into the channel.

  6. <-done receives the value and allows the main function to continue.

  7. The main function prints main: finished.

How to read the important code

go func() {

"go func, open parentheses, open curly brace."

This means: start this function in a new goroutine.

done <- true

"done channel, send true."

The <- operator sends a value into a channel.

<-done

"receive from done."

The <- operator receives a value from a channel.

Key takeaway

A goroutine runs concurrently, and a channel can be used to wait for its result.


Chapter 35 — Channels

Question

Given below is a code snippet that:

  • Creates a channel.

  • Sends a value through the channel.

  • Receives the value in another goroutine.

  • Demonstrates that an unbuffered channel synchronizes sender and receiver.

  • Shows the order in which the messages are printed.

What should be the output of the following code?

package main

import "fmt"

func main() {
	ch := make(chan int)

	go func() {
		fmt.Println("worker: before send")
		ch <- 42
		fmt.Println("worker: after send")
	}()

	fmt.Println("main: before receive")
	value := <-ch
	fmt.Println("main: received", value)
}

Answer

main: before receive
worker: before send
main: received 42
worker: after send

Step-by-step explanation

  1. make(chan int) creates an unbuffered channel.

  2. The goroutine prints worker: before send.

  3. ch <- 42 attempts to send 42.

  4. Because the channel is unbuffered, the send waits until another goroutine receives the value.

  5. The main function receives the value with <-ch.

  6. The receive completes, so the main function prints main: received 42.

  7. The worker continues and prints worker: after send.

How to read the important code

ch := make(chan int)

"ch colon equals make, open parenthesis, chan int, close parenthesis."

This creates an unbuffered channel that carries integers.

value := <-ch

"value colon equals receive from ch."

This receives a value from the channel and stores it in value.

Key takeaway

An unbuffered channel makes the sender and receiver wait for each other.


Chapter 36 — select

Question

Given below is a code snippet that:

  • Creates two channels.

  • Sends values through goroutines.

  • Uses select to wait for whichever channel is ready.

  • Demonstrates how select chooses a ready communication.

  • Shows how a timeout can be used to prevent waiting forever.

What should be the output of the following code?

package main

import (
	"fmt"
	"time"
)

func main() {
	fast := make(chan string)
	slow := make(chan string)

	go func() {
		time.Sleep(10 * time.Millisecond)
		fast <- "fast result"
	}()

	go func() {
		time.Sleep(30 * time.Millisecond)
		slow <- "slow result"
	}()

	select {
	case result := <-fast:
		fmt.Println(result)
	case result := <-slow:
		fmt.Println(result)
	case <-time.After(100 * time.Millisecond):
		fmt.Println("timeout")
	}
}

Answer

fast result

Step-by-step explanation

  1. fast and slow are channels that carry strings.

  2. The first goroutine sends "fast result" after 10 milliseconds.

  3. The second goroutine sends "slow result" after 30 milliseconds.

  4. select waits until one of its communication cases is ready.

  5. The fast channel becomes ready first.

  6. The first case runs, so the program prints fast result.

  7. The timeout case does not run because a result arrived before 100 milliseconds.

How to read the important code

select {
case result := <-fast:

"select, open curly brace, case result colon equals receive from fast, colon."

This means: wait until receiving from fast is possible, then execute this case.

case <-time.After(100 * time.Millisecond):

"case receive from time dot After, open parenthesis, one hundred times time dot Millisecond, close parenthesis, colon."

This means: if 100 milliseconds pass before another case is ready, execute the timeout case.

Key takeaway

select lets a goroutine wait for multiple channel operations and respond to whichever becomes ready first.


Chapter 37 — Mutexes, WaitGroups & Synchronization

Question

Given below is a code snippet that:

  • Starts multiple goroutines.

  • Uses a mutex to protect shared data.

  • Uses a WaitGroup to wait for all goroutines to finish.

  • Demonstrates why shared data must be synchronized.

  • Shows the final value after all increments are complete.

What should be the output of the following code?

package main

import (
	"fmt"
	"sync"
)

func main() {
	var (
		mu      sync.Mutex
		wg      sync.WaitGroup
		counter int
	)

	for i := 0; i < 3; i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()

			for j := 0; j < 1000; j++ {
				mu.Lock()
				counter++
				mu.Unlock()
			}
		}()
	}

	wg.Wait()
	fmt.Println(counter)
}

Answer

3000

Step-by-step explanation

  1. counter is shared by all three goroutines.

  2. Each goroutine increments it 1,000 times.

  3. mu.Lock() allows only one goroutine at a time to access the protected operation.

  4. counter++ reads the current value and increases it by one.

  5. mu.Unlock() allows another goroutine to enter.

  6. wg.Add(1) tells the WaitGroup that one goroutine has started.

  7. defer wg.Done() marks that goroutine as finished when it returns.

  8. wg.Wait() blocks until all three goroutines finish.

  9. Therefore, the final value is 3 × 1000 = 3000.

How to read the important code

var mu sync.Mutex

"var mu sync dot Mutex."

A mutex is a lock that allows only one goroutine at a time to enter a protected section of code.

defer wg.Done()

"defer wg dot Done, open parentheses, close parentheses."

This means: when the current function finishes, call wg.Done().

Key takeaway

A mutex protects shared data, and a WaitGroup waits for multiple goroutines to finish.


Chapter 38 — Context & Cancellation

Question

Given below is a code snippet that:

  • Creates a cancellable context.

  • Starts a goroutine that checks for cancellation.

  • Demonstrates how context.Context communicates cancellation.

  • Uses select to wait for work or cancellation.

  • Shows how a function can stop early when its context is cancelled.

What should be the output of the following code?

package main

import (
	"context"
	"fmt"
	"time"
)

func worker(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("worker: cancelled")
			return
		default:
			fmt.Println("worker: working")
			time.Sleep(10 * time.Millisecond)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	go worker(ctx)

	time.Sleep(25 * time.Millisecond)
	cancel()

	time.Sleep(10 * time.Millisecond)
	fmt.Println("main: finished")
}

Answer

worker: working
worker: working
worker: working
worker: cancelled
main: finished

Step-by-step explanation

  1. context.Background() creates a root context.

  2. context.WithCancel creates a child context and a cancellation function.

  3. The worker starts and prints worker: working.

  4. It repeats until the context is cancelled.

  5. After approximately 25 milliseconds, cancel() signals cancellation.

  6. The worker receives from ctx.Done() and prints worker: cancelled.

  7. return stops the worker.

  8. The main function prints main: finished.

How to read the important code

ctx, cancel := context.WithCancel(context.Background())

"ctx comma cancel colon equals context dot WithCancel, open parenthesis, context dot Background, open parentheses, close parentheses, close parenthesis."

This creates a context and a function that can cancel it.

case <-ctx.Done():

"case receive from ctx dot Done, open parentheses, close parentheses, colon."

This means: if the context is cancelled, execute this case.

Key takeaway

Context cancellation lets a function stop work when its caller no longer needs the result.


Chapter 39 — Concurrency Patterns

Question

Given below is a code snippet that:

  • Demonstrates a worker-pool pattern.

  • Sends jobs through a channel.

  • Uses multiple workers to process jobs.

  • Uses a WaitGroup to wait for all workers.

  • Uses a separate goroutine to close the results channel.

  • Shows how concurrent work can be coordinated.

What should be the output of the following code?

package main

import (
	"fmt"
	"sync"
)

func main() {
	jobs := make(chan int)
	results := make(chan int)

	var wg sync.WaitGroup

	worker := func() {
		defer wg.Done()

		for job := range jobs {
			results <- job * 2
		}
	}

	wg.Add(2)
	go worker()
	go worker()

	go func() {
		for i := 1; i <= 4; i++ {
			jobs <- i
		}
		close(jobs)
	}()

	go func() {
		wg.Wait()
		close(results)
	}()

	for result := range results {
		fmt.Println(result)
	}
}

Answer

2
4
6
8

Step-by-step explanation

  1. jobs carries integers to workers.

  2. results carries processed values back to the main function.

  3. Two workers run concurrently.

  4. Each worker receives jobs from jobs using range.

  5. Each job is multiplied by two and sent to results.

  6. The producer sends 1, 2, 3, and 4, then closes jobs.

  7. Closing jobs tells workers that no more jobs will arrive.

  8. Each worker finishes after processing all jobs.

  9. wg.Wait() waits for both workers to finish.

  10. close(results) tells the main function that no more results will arrive.

  11. The main function ranges over results and prints each value.

  12. The exact order is not guaranteed in general, but these four values are produced.

How to read the important code

for job := range jobs {

"for job colon equals range jobs, open curly brace."

This means: receive values from the jobs channel until it is closed.

close(results)

"close, open parenthesis, results, close parenthesis."

This means: signal that no more values will be sent on the results channel.

Key takeaway

A worker pool divides jobs among goroutines and uses channels to coordinate work and results.


Chapter 40 — Race Conditions & Race Detector

Question

Given below is a code snippet that:

  • Demonstrates a shared variable accessed by multiple goroutines.

  • Shows why unsynchronized access is unsafe.

  • Demonstrates how to fix the race with a mutex.

  • Shows the difference between a race-free program and a race-prone program.

  • Teaches the purpose of Go's race detector.

What should be the output of the following code?

package main

import (
	"fmt"
	"sync"
)

func main() {
	var (
		mu      sync.Mutex
		wg      sync.WaitGroup
		counter int
	)

	for i := 0; i < 5; i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()

			for j := 0; j < 1000; j++ {
				mu.Lock()
				counter++
				mu.Unlock()
			}
		}()
	}

	wg.Wait()
	fmt.Println("counter:", counter)
}

Answer

counter: 5000

Step-by-step explanation

  1. Five goroutines share the same counter.

  2. Each goroutine increments it 1,000 times.

  3. Without synchronization, two goroutines could read the same old value and overwrite each other's updates.

  4. That problem is called a race condition: the result depends on the timing of concurrent operations.

  5. The mutex ensures that only one goroutine modifies counter at a time.

  6. Every increment is therefore preserved.

  7. wg.Wait() ensures all goroutines finish before the final print.

  8. The final value is 5 × 1000 = 5000.

  9. Go's race detector can detect unsynchronized conflicting accesses when the program is run with go run -race or go test -race.

How to read the important code

go run -race main.go

"go run dash race main dot go."

This runs the program with Go's race detector, which checks for certain unsafe concurrent memory accesses.

mu.Lock()
counter++
mu.Unlock()

"mu dot Lock, open parentheses, close parentheses. counter plus plus. mu dot Unlock, open parentheses, close parentheses."

This means: lock the shared data, update it, then unlock it.

Key takeaway

A race condition can corrupt shared data, and Go's race detector helps find unsafe concurrent access.

GO 21 - 30

Chapter 21 — Packages & Visibility

Question

Given below is a code snippet that:

  • Demonstrates how a package is declared and imported.

  • Demonstrates exported and unexported identifiers.

  • Demonstrates how main calls a function from another package.

  • Demonstrates how package visibility works.

GO 11 - 20

Absolutely. I understand the correction now.

From here onward, the explanation is the actual lesson. The code is only the material you will predict. I will assume you are a complete programming beginner and will not skip things such as _, err, *, &, :=, [], map, receivers, interfaces, etc.

For each chapter: ONE question → ONE code snippet → output → line-by-line explanation of every component.


Chapter 11 — Arrays

Question

What should be the output of this code?

package main

import "fmt"

func main() {
	scores := [4]int{70, 80, 90, 100}

	fmt.Println(scores[0])

	scores[1] = 85

	fmt.Println(scores)

	for i := 0; i < len(scores); i++ {
		fmt.Println(i, scores[i])
	}
}

Answer

70
[70 85 90 100]
0 70
1 85
2 90
3 100

Explanation

Line 1

package main
  • package is a Go keyword that tells Go which package this code belongs to.

  • A package is simply a way of organizing Go code.

  • main is a special package name.

  • When we are creating a program that can run by itself, we normally use package main.

So this line essentially tells Go:

"This is an executable program."


Line 2

import "fmt"
  • import tells Go that we want to use code from another package.

  • "fmt" is Go's standard package for formatting and printing.

  • We need fmt because later we use fmt.Println().

Think of it as:

"Bring the printing tools from the fmt package into this program."


Line 4

func main() {

There are several components here.

  • func means we are defining a function.

  • main is the name of the function.

  • () are parentheses. They contain the function's inputs. Here there are no inputs, so they are empty.

  • { is an opening curly brace. It marks the beginning of the function's code.

main() is special because Go starts executing our program from this function.


Line 5

scores := [4]int{70, 80, 90, 100}

This line is very important.

scores

This is the variable name.

We are calling our variable scores.

:=

This is called the short variable declaration.

It means:

"Create a new variable and put this value into it."

So:

scores := ...

means:

"Create a variable called scores."

[4]int

This describes the type of data.

  • [4] means an array containing exactly 4 elements.

  • int means each element is an integer.

So:

[4]int

means:

"An array of exactly four integers."

{70, 80, 90, 100}

These are the four values stored in the array.

So the complete line creates:

Index:   0   1   2    3
Value:  70  80  90  100

Remember: Go indexes start at 0.


Line 7

fmt.Println(scores[0])

Break it down.

fmt

The package we imported earlier.

.

The dot means:

"Access something belonging to fmt."

Println

A function provided by fmt.

Println prints something and then moves to the next line.

scores[0]

  • scores is our array.

  • [0] means "give me the element at index 0."

Index 0 contains 70.

Therefore:

fmt.Println(scores[0])

prints:

70

Line 9

scores[1] = 85
  • scores[1] means the element at index 1.

  • Index 1 currently contains 80.

  • = means assignment.

  • 85 is the new value.

So Go changes:

[70 80 90 100]

into:

[70 85 90 100]

Line 11

fmt.Println(scores)

fmt.Println prints the entire array.

The array is now:

[70 85 90 100]

So that is the second output.


Line 13

for i := 0; i < len(scores); i++ {

This is a for loop.

There are three important parts separated by semicolons.

i := 0

Create a variable called i and start it at 0.

;

Separates the three parts of the loop.

i < len(scores)

This is the condition.

  • len(scores) asks for the length of the array.

  • The array contains 4 elements.

  • Therefore len(scores) is 4.

  • The condition becomes:

i < 4

The loop continues while that is true.

i++

++ means:

Increase i by 1.

So i goes:

0 → 1 → 2 → 3 → 4

When it becomes 4, 4 < 4 is false, so the loop stops.

{

The opening curly brace marks the code belonging to the loop.


Line 14

fmt.Println(i, scores[i])

There are two things being printed.

i

The current loop number.

scores[i]

This means:

"Get the array element whose index is the current value of i."

When i = 0:

scores[0] → 70

When i = 1:

scores[1] → 85

And so on.

Therefore the loop prints:

0 70
1 85
2 90
3 100

Final lines

}

The first } closes the for loop.

}

The second } closes the main() function.


Chapter 12 — Slices

Question

What should be the output of this code?

package main

import "fmt"

func main() {
	fruits := []string{"apple", "banana", "mango"}

	fmt.Println(len(fruits))

	fruits = append(fruits, "orange")

	fmt.Println(fruits)
	fmt.Println(fruits[1:3])

	fruits[0] = "grape"

	fmt.Println(fruits)
}

Answer

3
[apple banana mango orange]
[banana mango]
[grape banana mango orange]

Explanation

Line 1

package main

package declares the package.

main means this is an executable program.


Line 2

import "fmt"
  • import brings another package into our program.

  • fmt provides functions for formatted input/output.

  • We need it for fmt.Println.


Line 4

func main() {
  • func defines a function.

  • main is the function name.

  • () means it takes no arguments.

  • { starts the function body.

Go begins executing our program inside main().


Line 5

fruits := []string{"apple", "banana", "mango"}

fruits

Variable name.

:=

Create a new variable and initialize it.

[]string

This means:

A slice containing strings.

Unlike [3]string, the size is not fixed.

{...}

Contains the initial elements.

So we have:

Index:   0        1         2
Value: apple    banana    mango

Line 7

fmt.Println(len(fruits))

len(fruits) asks:

"How many elements are currently in fruits?"

There are 3.

So it prints:

3

Line 9

fruits = append(fruits, "orange")

This line has several pieces.

append

append is a built-in Go function used to add elements to a slice.

append(fruits, "orange")

Means:

"Take the fruits slice and add "orange" to it."

The result becomes:

[apple banana mango orange]

fruits =

We assign the resulting slice back to fruits.

So now fruits contains four elements.


Line 11

fmt.Println(fruits)

Prints:

[apple banana mango orange]

Line 12

fmt.Println(fruits[1:3])

This is slice expression syntax.

fruits[1:3]

means:

Start at index 1 and stop before index 3.

The indexes are:

0 → apple
1 → banana
2 → mango
3 → orange

Therefore indexes 1 and 2 are selected:

[banana mango]

Line 14

fruits[0] = "grape"
  • fruits[0] means the first element.

  • = means replace its value.

  • "grape" is the new string.

So:

[apple banana mango orange]

becomes:

[grape banana mango orange]

Line 16

fmt.Println(fruits)

Prints the final slice:

[grape banana mango orange]

Chapter 13 — Maps

Question

What should be the output of this code?

package main

import "fmt"

func main() {
	ages := map[string]int{
		"Alice": 25,
		"Bob":   30,
	}

	fmt.Println(ages["Alice"])

	ages["Alice"] = 26
	ages["Carol"] = 22

	age, ok := ages["Bob"]

	fmt.Println(age, ok)

	delete(ages, "Bob")

	age, ok = ages["Bob"]

	fmt.Println(age, ok)
}

Answer

25
30 true
0 false

Explanation

Line 1

package main

Declares the executable main package.


Line 2

import "fmt"

Imports the fmt package so we can use fmt.Println.


Line 4

func main() {

Defines the main function.

Go begins execution here.


Line 5

ages := map[string]int{

This creates a map.

ages

Variable name.

:=

Create and initialize a new variable.

map

map is Go's key-value data structure.

Think:

key → value

[string]int

This means:

The keys are strings, and the values are integers.

So this map can contain:

"Alice" → 25
"Bob"   → 30

{

Starts the map's contents.


Lines 6–7

"Alice": 25,
"Bob":   30,

Each entry has:

key : value

So:

"Alice" → 25
"Bob"   → 30

Line 10

fmt.Println(ages["Alice"])

ages["Alice"] means:

Look inside the map using "Alice" as the key.

The value is 25.

Therefore:

25

Line 12

ages["Alice"] = 26

Find the "Alice" key and replace its value.

So:

Alice → 25

becomes:

Alice → 26

Line 13

ages["Carol"] = 22

There is no "Carol" key yet.

So Go creates it:

Carol → 22

Line 15

age, ok := ages["Bob"]

This is extremely important.

A map lookup can return two values.

age, ok := ages["Bob"]

means:

"Give me Bob's value, and also tell me whether Bob exists in the map."

age

Receives the actual value.

Bob is 30.

,

The comma separates the two variables.

ok

Receives a boolean:

  • true → key exists.

  • false → key doesn't exist.

:=

Creates both new variables.

Therefore:

age = 30
ok = true

Line 17

fmt.Println(age, ok)

Prints both values:

30 true

Line 19

delete(ages, "Bob")

delete is a built-in Go function.

It removes a key from a map.

So "Bob" is removed.


Line 21

age, ok = ages["Bob"]

This time we use = rather than :=.

Why?

Because age and ok already exist.

The lookup asks for Bob again.

But Bob was deleted.

Therefore:

age = 0
ok = false

0 is the zero value for an int.


Line 23

fmt.Println(age, ok)

Prints:

0 false

The important lesson is that false tells us Bob wasn't found.


Chapter 14 — Structs

Question

What should be the output of this code?

package main

import "fmt"

type User struct {
	Name  string
	Age   int
	Admin bool
}

func main() {
	user := User{
		Name:  "Ravi",
		Age:   30,
		Admin: false,
	}

	fmt.Println(user.Name, user.Age, user.Admin)

	user.Age = user.Age + 1
	user.Admin = true

	fmt.Println(user.Name, user.Age, user.Admin)
}

Answer

Ravi 30 false
Ravi 31 true

Explanation

Line 1

package main

Declares the executable package.


Line 2

import "fmt"

Imports the fmt package for printing.


Line 4

type User struct {

This defines our own type.

type

Go keyword used to define a new type.

User

The name of our new type.

struct

A struct allows us to group related data together.

So we're saying:

"Create a type called User that contains several pieces of information."


Lines 5–7

Name  string
Age   int
Admin bool

These are fields of the struct.

A field is a piece of data belonging to the struct.

So every User has:

Name  → string
Age   → integer
Admin → true/false

Line 10

user := User{
  • user is the variable.

  • := creates it.

  • User{ creates a value of the User type.

  • { starts the field values.


Lines 11–13

Name:  "Ravi",
Age:   30,
Admin: false,

The syntax is:

field: value

Therefore:

Name  = "Ravi"
Age   = 30
Admin = false

Line 16

fmt.Println(user.Name, user.Age, user.Admin)

The dot . accesses a field.

So:

user.Name

means:

Get the Name field from user.

Similarly:

user.Age
user.Admin

get the other fields.

Therefore it prints:

Ravi 30 false

Line 18

user.Age = user.Age + 1

Read this from the right side first.

user.Age + 1

Current age is 30.

So:

30 + 1 = 31

Then:

user.Age = 31

stores 31 back into the Age field.


Line 19

user.Admin = true

Changes:

Admin = false

to:

Admin = true

Line 21

fmt.Println(user.Name, user.Age, user.Admin)

The values are now:

Ravi
31
true

Therefore:

Ravi 31 true

Chapter 15 — Pointers

Question

What should be the output of this code?

package main

import "fmt"

func increase(n *int) {
	*n = *n + 10
}

func main() {
	score := 50

	p := &score

	fmt.Println(score)
	fmt.Println(*p)

	*p = 75

	fmt.Println(score)

	increase(&score)

	fmt.Println(score)
}

Answer

50
50
75
85

Explanation

Line 1

package main

Declares the executable package.


Line 2

import "fmt"

Imports the printing package.


Line 4

func increase(n *int) {

This line needs careful attention.

func

Defines a function.

increase

Function name.

n

The parameter's name.

A parameter is a variable that receives a value when a function is called.

*int

This means:

n is a pointer to an integer.

A pointer stores the memory address of another value.

{

Starts the function body.


Line 5

*n = *n + 10

There are two * symbols here, and both mean:

Follow the pointer and access the value stored at that address.

Suppose n points to score.

Then:

*n

means:

The value of score.

So:

*n = *n + 10

means:

Take the original value, add 10, and store it back.

If the value is 75:

75 + 10 = 85

Line 8

func main() {

Defines the program's starting function.


Line 9

score := 50

Creates an integer variable:

score = 50

Line 11

p := &score

This is another very important line.

p

Variable name.

:=

Create a new variable.

&

The ampersand here means:

Get the memory address of score.

So p stores the address where score lives.

Conceptually:

p ──────► score
          50

Line 13

fmt.Println(score)

Prints the value of score:

50

Line 14

fmt.Println(*p)

p contains the address of score.

*p means:

Go to that address and get the value stored there.

That value is 50.

So it prints:

50

Line 16

*p = 75

*p refers to the value at the address stored in p.

That is score.

Therefore this effectively changes:

score = 50

to:

score = 75

Line 18

fmt.Println(score)

score is now 75.

So:

75

Line 20

increase(&score)

We call the increase function.

&score

Means:

Give the function the address of score.

The function parameter n receives that address.

So inside the function:

n ──────► score
          75

Inside increase

*n = *n + 10

*n is the original score.

So:

75 + 10 = 85

The original score becomes 85.


Line 22

fmt.Println(score)

Prints:

85

Chapter 16 — Methods & Receivers

Question

What should be the output of this code?

package main

import "fmt"

type Counter struct {
	Value int
}

func (c Counter) Double() int {
	return c.Value * 2
}

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

func main() {
	counter := Counter{Value: 5}

	fmt.Println(counter.Double())

	counter.Increment()
	counter.Increment()

	fmt.Println(counter.Value)
	fmt.Println(counter.Double())
}

Answer

10
7
14

Explanation

Line 1

package main

Declares the executable package.


Line 2

import "fmt"

Imports the fmt package for printing.


Line 4

type Counter struct {

Creates a new type called Counter.

It is a struct, meaning it groups related data.


Line 5

Value int

The Counter struct has one field:

Value → int

Line 8

func (c Counter) Double() int {

This line looks complicated, so break it apart.

func

Defines a function.

(c Counter)

This is called the receiver.

A receiver connects a method to a type.

It means:

This method belongs to Counter.

c is the variable representing the Counter value inside the method.

Double

The method's name.

()

The method takes no additional arguments.

int

The method returns an integer.


Line 9

return c.Value * 2

return

Sends a value back to the code that called the method.

c.Value

Gets the Value field from the receiver.

* 2

Multiplies it by 2.

If Value is 5:

5 × 2 = 10

Line 12

func (c *Counter) Increment() {

This is similar to the previous method, but notice:

*c Counter

Actually the syntax is:

c *Counter

Meaning:

c is a pointer to a Counter.

Because this method receives a pointer to the original Counter, it can modify it.


Line 13

c.Value++

c.Value accesses the Value field.

++ means:

Increase by 1.

So:

5 → 6

Line 16

counter := Counter{Value: 5}

Creates a Counter value.

Its Value field is 5.


Line 18

fmt.Println(counter.Double())

The dot:

counter.Double()

means:

Call the Double method belonging to counter.

The method receives the value 5.

It calculates:

5 × 2 = 10

So the output is:

10

Line 20

counter.Increment()

Calls the Increment method.

It increases Value:

5 → 6

Line 21

counter.Increment()

Calls it again:

6 → 7

Line 23

fmt.Println(counter.Value)

The current value is 7.

So:

7

Line 24

fmt.Println(counter.Double())

Double() now receives a Value of 7.

Therefore:

7 × 2 = 14

Output:

14

Chapter 17 — Interfaces

Question

What should be the output of this code?

package main

import "fmt"

type Speaker interface {
	Speak() string
}

type Dog struct {
	Name string
}

type Person struct {
	Name string
}

func (d Dog) Speak() string {
	return d.Name + " says Woof"
}

func (p Person) Speak() string {
	return p.Name + " says Hello"
}

func introduce(s Speaker) {
	fmt.Println(s.Speak())
}

func main() {
	dog := Dog{Name: "Bruno"}
	person := Person{Name: "Ravi"}

	introduce(dog)
	introduce(person)
}

Answer

Bruno says Woof
Ravi says Hello

Explanation

Line 1

package main

Declares the executable package.


Line 2

import "fmt"

Imports fmt for printing.


Line 4

type Speaker interface {

This creates an interface called Speaker.

An interface describes behavior.

Here we're saying:

"A Speaker must have a Speak() method that returns a string."


Line 5

Speak() string

This describes the required method.

  • Speak → method name.

  • () → no arguments.

  • string → it must return a string.

Notice there is no function body {} here.

The interface only describes what must exist.


Line 8

type Dog struct {

Creates a Dog struct.


Line 9

Name string

Every Dog has a string called Name.


Line 12

type Person struct {

Creates another type called Person.


Line 13

Name string

Every Person also has a Name.


Line 16

func (d Dog) Speak() string {

Defines the Speak method for Dog.

Because Dog has:

Speak() string

it satisfies the Speaker interface.

Go does this automatically.

You don't write:

implements Speaker

Line 17

return d.Name + " says Woof"

Gets the dog's name and joins it with another string.

If the name is "Bruno":

"Bruno" + " says Woof"

becomes:

"Bruno says Woof"

Lines 20–23

func (p Person) Speak() string {
	return p.Name + " says Hello"
}

This gives Person its own Speak() method.

Therefore Person also satisfies Speaker.


Line 26

func introduce(s Speaker) {

Defines a function named introduce.

The parameter is:

s Speaker

This means:

s can contain any value that satisfies the Speaker interface.

It could be a Dog, Person, or another type with the required Speak() string method.


Line 27

fmt.Println(s.Speak())

Calls the Speak() method.

The important thing is that Go chooses the appropriate method based on the actual value.

If s contains a Dog:

Dog.Speak()

If s contains a Person:

Person.Speak()

Line 30

dog := Dog{Name: "Bruno"}

Creates a Dog whose name is Bruno.


Line 31

person := Person{Name: "Ravi"}

Creates a Person whose name is Ravi.


Line 33

introduce(dog)

Passes the Dog to introduce.

The parameter expects a Speaker.

Dog satisfies Speaker because it has:

Speak() string

Therefore Dog.Speak() runs.

Output:

Bruno says Woof

Line 34

introduce(person)

Person also satisfies Speaker.

Therefore Person.Speak() runs.

Output:

Ravi says Hello

Chapter 18 — Type Assertions & Type Switches

Question

What should be the output of this code?

package main

import "fmt"

func describe(value any) {
	switch v := value.(type) {
	case string:
		fmt.Println("string:", v)
	case int:
		fmt.Println("int:", v)
	default:
		fmt.Println("other")
	}
}

func main() {
	var value any = "Go"

	text, ok := value.(string)
	fmt.Println(text, ok)

	number, ok := value.(int)
	fmt.Println(number, ok)

	describe(value)
	describe(42)
}

Answer

Go true
0 false
string: Go
int: 42

Explanation

Line 1

package main

Declares the executable package.


Line 2

import "fmt"

Imports the printing package.


Line 4

func describe(value any) {

Defines a function called describe.

The parameter is:

value any

any means:

This variable can hold a value of any type.

any is another name for Go's empty interface, interface{}.


Line 5

switch v := value.(type) {

This is a type switch.

It asks:

"What type of value is currently stored inside value?"

switch

Used to choose between different cases.

v :=

Creates a variable called v.

v will contain the value with its actual type.

value.(type)

Inside a type switch, this asks for the value's actual type.


Line 6

case string:

If the value is a string, execute this case.


Line 7

fmt.Println("string:", v)

Prints the word string: and the actual string.


Line 8

case int:

If the value is an integer, this case runs.


Line 9

fmt.Println("int:", v)

Prints int: and the integer.


Line 10

default:

If none of the listed types match, default executes.


Line 11

fmt.Println("other")

Prints other.


Line 14

var value any = "Go"

This uses another way to declare a variable.

var

Tells Go:

"Declare a variable."

value

Variable name.

any

Its type.

=

Assigns the value.

"Go"

The actual string.

So value contains a string.


Line 16

text, ok := value.(string)

This is a type assertion.

The important part is:

value.(string)

It asks:

"Is the value inside value actually a string?"

There are two possible results here.

text

Receives the actual string.

So:

text = "Go"

ok

Receives a boolean.

Because it is a string:

ok = true

This two-value pattern is extremely important in Go.


Line 17

fmt.Println(text, ok)

Prints:

Go true

Line 19

number, ok := value.(int)

Again, we're asking what type the value has.

But this time we're asking:

"Is it an integer?"

It isn't.

Therefore:

number = 0
ok = false

Why 0?

Because 0 is the zero value for an integer.


Line 20

fmt.Println(number, ok)

Prints:

0 false

Line 22

describe(value)

value contains "Go".

The type switch sees that it is a string.

Therefore:

string: Go

Line 23

describe(42)

42 is an integer.

Therefore the int case runs:

int: 42

Chapter 19 — Error Handling

Question

What should be the output of this code?

package main

import (
	"errors"
	"fmt"
)

func withdraw(balance int, amount int) (int, error) {
	if amount > balance {
		return balance, errors.New("insufficient balance")
	}

	return balance - amount, nil
}

func main() {
	balance, err := withdraw(100, 30)

	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Balance:", balance)
	}

	balance, err = withdraw(balance, 80)

	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Balance:", balance)
	}
}

Answer

Balance: 70
Error: insufficient balance

Explanation

Line 1

package main

Declares the executable package.


Lines 2–5

import (
	"errors"
	"fmt"
)

We are importing two packages.

errors

Provides functions for creating error values.

fmt

Provides printing functions.

Parentheses allow us to import multiple packages together.


Line 7

func withdraw(balance int, amount int) (int, error) {

Defines a function named withdraw.

It receives two parameters:

balance → int
amount  → int

Notice this part:

(int, error)

That means the function returns two values:

  1. an int

  2. an error


Line 8

if amount > balance {

Checks whether the requested withdrawal is greater than the available balance.

For the first call:

30 > 100

is false.


Line 9

return balance, errors.New("insufficient balance")

This line would execute only if the condition were true.

return

Sends two values back to the caller.

balance

Returns the current balance.

errors.New(...)

Creates an error.

The error contains:

insufficient balance

Line 12

return balance - amount, nil

This is the successful case.

balance - amount calculates the new balance.

nil means:

There is no error.

So a successful function call returns:

result, nil

Line 16

balance, err := withdraw(100, 30)

Calls the function.

The function receives:

balance = 100
amount = 30

It returns:

70, nil

So:

balance = 70
err = nil

Line 18

if err != nil {

This asks:

"Does an error exist?"

nil means no error.

Here:

err = nil

Therefore:

err != nil

is false.


Line 19

fmt.Println("Error:", err)

This is skipped because the condition was false.


Line 20

} else {

Because the if condition was false, Go executes the else block.


Line 21

fmt.Println("Balance:", balance)

balance is 70.

So:

Balance: 70

Line 24

balance, err = withdraw(balance, 80)

Notice:

=

instead of:

:=

Why?

Because balance and err already exist.

We're assigning new values to existing variables.

The call becomes:

withdraw(70, 80)

Now:

80 > 70

is true.

So the function returns:

70, error

Line 26

if err != nil {

This time err contains an error.

Therefore:

err != nil

is true.


Line 27

fmt.Println("Error:", err)

The error's text is:

insufficient balance

So Go prints:

Error: insufficient balance

The most important pattern from this chapter

When you see:

value, err := someFunction()

read it as:

"Call the function, receive the result in value, and receive information about whether something went wrong in err."

Then normally:

if err != nil {

means:

"If something went wrong, handle the error."


Chapter 20 — defer, panic & recover

Question

What should be the output of this code?

package main

import "fmt"

func safeOperation() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered:", r)
		}
	}()

	defer fmt.Println("Cleanup")

	fmt.Println("Operation started")

	panic("something went wrong")

	fmt.Println("This will not run")
}

func main() {
	fmt.Println("Before")

	safeOperation()

	fmt.Println("After")
}

Answer

Before
Operation started
Cleanup
Recovered: something went wrong
After

Explanation

Line 1

package main

Declares the executable package.


Line 2

import "fmt"

Imports the fmt package so we can print output.


Line 4

func safeOperation() {

Defines a function called safeOperation.

It takes no arguments because () is empty.


Line 5

defer func() {

This line contains two important ideas.

defer

defer means:

Schedule this function to run when the current function is finishing.

It does not execute immediately.

func()

This creates an anonymous function—a function without a name.

So this means:

"Schedule this unnamed function to run later."


Line 6

if r := recover(); r != nil {

This is complicated, so break it down.

recover()

recover() is a built-in Go function that can capture a panic.

If a panic is happening, recover() gives us the panic value.

r :=

Creates a variable called r containing whatever recover() returns.

;

Separates the variable declaration from the condition.

r != nil

Means:

"Is r not empty?"

If a panic was recovered, this is true.


Line 7

fmt.Println("Recovered:", r)

Prints the recovered panic value.

In our program the panic value is:

something went wrong

So this eventually prints:

Recovered: something went wrong

Line 8

}()

This is important.

Earlier we created an unnamed function:

func() {
    ...
}

The () at the end calls that function.

Because it is preceded by defer, Go schedules that call for later.


Line 10

defer fmt.Println("Cleanup")

This schedules:

fmt.Println("Cleanup")

to run when safeOperation() finishes.

It does not print Cleanup immediately.


Line 12

fmt.Println("Operation started")

This executes immediately.

Therefore:

Operation started

is printed.


Line 14

panic("something went wrong")

panic means:

Something has gone seriously wrong; stop normal execution and begin panic handling.

The panic value is:

something went wrong

Line 16

fmt.Println("This will not run")

This line is never executed.

Why?

Because panic() happened immediately before it.

Normal execution of safeOperation() stops.


What happens after panic()?

Go starts executing deferred functions.

We registered two deferred actions.

The second one was:

defer fmt.Println("Cleanup")

So it runs and prints:

Cleanup

Then the earlier deferred recovery function runs.

Inside it:

recover()

captures:

something went wrong

So it prints:

Recovered: something went wrong

Because the panic was recovered, safeOperation() finishes without crashing the whole program.


Line 20

func main() {

Defines the program's starting function.


Line 21

fmt.Println("Before")

Prints:

Before

This happens before safeOperation() is called.


Line 23

safeOperation()

Calls the function.

The function prints:

Operation started

Then panics.

The deferred cleanup and recovery code runs.


Line 25

fmt.Println("After")

Because the panic was successfully recovered inside safeOperation(), execution returns to main() and continues.

Therefore:

After

is printed.


One crucial rule to remember

defer means:

"Run this later when the surrounding function is finishing."

panic means:

"Stop normal execution because something has gone seriously wrong."

recover means:

"Catch a panic while inside a deferred function."

And one very important detail from this example:

defer A()
defer B()

runs as:

B()
A()

In other words, deferred calls execute in reverse order of registration.