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
httptest.NewRequestcreates a test HTTP request. It does not send a real network request.httptest.NewRecordercaptures the response so we can inspect it.server.ServeHTTP(rec, req)runs the handler.For
id=1, the handler writes status200and encodes the user as JSON.json.NewEncoder(w).Encode(...)converts the struct into JSON and adds a newline.For
id=2, the condition is true, sohttp.Errorwrites the error response and status404.returnstops 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: abc123Step-by-step explanation
addRequestID(handler)wraps the original handler.logging(...)wraps that result.The outer logging middleware runs first and prints
before handler.It calls the next middleware.
The request ID middleware sets the response header and calls the original handler.
The original handler prints
inside handlerand writesHello.Control returns to the logging middleware, which prints
after handler.rec.Body.String()contains"Hello\n"becausefmt.Fprintlnadds 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 Allowedfor 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 AllowedStep-by-step explanation
http.NewServeMux()creates a router."GET /users/{id}"matches GET requests whose path begins with/users/.{id}is a path wildcard.r.PathValue("id")retrieves the value captured by that wildcard."POST /users"matches only POST requests to/users.The DELETE request does not match an allowed method for that route, so the router returns
405.rec.Coderecords 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: finishedStep-by-step explanation
make(chan bool)creates a channel that carries boolean values.go func() { ... }()starts the anonymous function in a goroutine.The main function prints
main: waiting.The worker prints its two messages.
done <- truesendstrueinto the channel.<-donereceives the value and allows the main function to continue.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 sendStep-by-step explanation
make(chan int)creates an unbuffered channel.The goroutine prints
worker: before send.ch <- 42attempts to send42.Because the channel is unbuffered, the send waits until another goroutine receives the value.
The main function receives the value with
<-ch.The receive completes, so the main function prints
main: received 42.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
selectto wait for whichever channel is ready.Demonstrates how
selectchooses 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 resultStep-by-step explanation
fastandsloware channels that carry strings.The first goroutine sends
"fast result"after 10 milliseconds.The second goroutine sends
"slow result"after 30 milliseconds.selectwaits until one of its communication cases is ready.The fast channel becomes ready first.
The first case runs, so the program prints
fast result.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
selectlets 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
WaitGroupto 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
3000Step-by-step explanation
counteris shared by all three goroutines.Each goroutine increments it 1,000 times.
mu.Lock()allows only one goroutine at a time to access the protected operation.counter++reads the current value and increases it by one.mu.Unlock()allows another goroutine to enter.wg.Add(1)tells theWaitGroupthat one goroutine has started.defer wg.Done()marks that goroutine as finished when it returns.wg.Wait()blocks until all three goroutines finish.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
WaitGroupwaits 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.Contextcommunicates cancellation.Uses
selectto 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: finishedStep-by-step explanation
context.Background()creates a root context.context.WithCancelcreates a child context and a cancellation function.The worker starts and prints
worker: working.It repeats until the context is cancelled.
After approximately 25 milliseconds,
cancel()signals cancellation.The worker receives from
ctx.Done()and printsworker: cancelled.returnstops the worker.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
WaitGroupto 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
8Step-by-step explanation
jobscarries integers to workers.resultscarries processed values back to the main function.Two workers run concurrently.
Each worker receives jobs from
jobsusingrange.Each job is multiplied by two and sent to
results.The producer sends
1,2,3, and4, then closesjobs.Closing
jobstells workers that no more jobs will arrive.Each worker finishes after processing all jobs.
wg.Wait()waits for both workers to finish.close(results)tells the main function that no more results will arrive.The main function ranges over
resultsand prints each value.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: 5000Step-by-step explanation
Five goroutines share the same
counter.Each goroutine increments it 1,000 times.
Without synchronization, two goroutines could read the same old value and overwrite each other's updates.
That problem is called a race condition: the result depends on the timing of concurrent operations.
The mutex ensures that only one goroutine modifies
counterat a time.Every increment is therefore preserved.
wg.Wait()ensures all goroutines finish before the final print.The final value is
5 × 1000 = 5000.Go's race detector can detect unsynchronized conflicting accesses when the program is run with
go run -raceorgo 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.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.