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.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.