Yes. For Block 4, I’ll make it as a complete Blogspot-ready learning article, not as a prompt.
I’ll also make sure every MCQ, practical question, debugging task, and final challenge has its answer/solution included, so you can paste the whole thing directly into a Blogspot post.
Go Programming — Block 4
Professional Skills, Concurrency, APIs, Security, Testing & Performance
Chapters covered:
Goroutines and Concurrency
Channels and Synchronization
Context, Timeouts, and Cancellation
Networking and External APIs
File Uploads and Downloads
Logging and Application Monitoring
Advanced Testing and Benchmarks
Security, Secrets, and Dependency Management
Docker and Deployment Workflows
Performance, Refactoring, and Maintenance
PART 1 — HIGH-VALUE MCQs
Chapter 31 — Goroutines and Concurrency
MCQ 1
You have a Go program that needs to perform two independent tasks. You want the tasks to be able to run concurrently.
Which Go feature is designed for this?
A. struct
B. goroutine
C. map
D. package
Answer: B — goroutine
A goroutine allows a function to run concurrently with other work.
The basic syntax is:
go doSomething()The go keyword tells Go to start the function as a goroutine.
MCQ 2
What is the main benefit of concurrency?
A. It automatically makes every program faster
B. It allows multiple tasks to make progress without waiting for one another unnecessarily
C. It removes all programming errors
D. It eliminates the need for functions
Answer: B
Concurrency is particularly useful when a program spends time waiting for things such as network responses, files, or other operations.
Important: concurrency does not automatically mean a program will become faster.
MCQ 3
What is potentially wrong with this program?
package main
import "fmt"
func main() {
go fmt.Println("Hello")
}A. fmt.Println cannot be used with goroutines
B. The program may finish before the goroutine gets a chance to print
C. go only works with variables
D. Nothing can ever go wrong
Answer: B
main() can finish immediately. When the main program exits, the process ends, potentially before the goroutine prints anything.
This teaches an important practical lesson:
Starting concurrent work does not mean your program automatically waits for it.
MCQ 4
Which tool is commonly used to wait for a group of goroutines to finish?
A. sync.WaitGroup
B. fmt.Wait()
C. time.WaitGroup
D. os.Wait()
Answer: A
sync.WaitGroup can coordinate goroutines when you need to wait until several tasks have completed.
Chapter 32 — Channels and Synchronization
MCQ 5
What is a channel primarily used for?
A. Storing HTML files
B. Communicating between goroutines
C. Creating databases
D. Formatting strings
Answer: B
A channel provides a way for goroutines to send and receive values.
Example:
messages := make(chan string)
messages <- "Hello"MCQ 6
What does this do?
message := <-messagesA. Sends message into the channel
B. Receives a value from messages
C. Deletes the channel
D. Creates a new goroutine
Answer: B
<-messages means receive a value from the channel.
MCQ 7
Why can shared data become dangerous when multiple goroutines modify it?
A. Go cannot store numbers
B. Operations can interfere with one another and produce incorrect results
C. Goroutines cannot use variables
D. Variables disappear automatically
Answer: B
For example, two goroutines modifying the same counter at the same time can cause a race condition.
Go provides tools such as:
channels
mutexes
atomic operations
to coordinate access to shared data.
Chapter 33 — Context, Timeouts, and Cancellation
MCQ 8
Why would an HTTP request need a timeout?
A. To make the URL shorter
B. To prevent a program from waiting indefinitely for a slow operation
C. To encrypt the request
D. To increase the screen resolution
Answer: B
Real applications communicate with networks that can become slow or unavailable.
A timeout prevents one operation from holding up the program forever.
MCQ 9
What is context.Context commonly used for?
A. Managing cancellation, deadlines, and request-scoped information
B. Creating CSS
C. Drawing graphics
D. Replacing all variables
Answer: A
For example, a web request might be cancelled because the user disconnected.
A context lets related work know that it should stop.
MCQ 10
Suppose an API request has a 5-second deadline.
What should happen if the server does not respond within that period?
A. The program should continue waiting forever
B. The operation should be cancelled or treated as timed out
C. The computer should shut down
D. The API should automatically send a successful response
Answer: B
Timeouts are a basic reliability mechanism.
Chapter 34 — Networking and External APIs
MCQ 11
What is an API?
A. A way for software systems to communicate with each other
B. A type of computer monitor
C. A programming language
D. A database table
Answer: A
For example:
Your Go program
↓
HTTP request
↓
Weather API
↓
JSON response
↓
Your Go programMCQ 12
Which HTTP method is commonly used to retrieve data?
A. GET
B. SEND
C. RECEIVE
D. FETCHDATA
Answer: A — GET
Common HTTP methods include:
GET— retrievePOST— create/send dataPUT— replace/updatePATCH— partially updateDELETE— remove
MCQ 13
An API returns this JSON:
{
"name": "Go",
"year": 2009
}Which Go feature is commonly used to convert JSON into Go data?
A. encoding/json
B. fmt/html
C. database/css
D. net/text
Answer: A
Go's standard library includes the encoding/json package.
Chapter 35 — File Uploads and Downloads
MCQ 14
A website allows a user to upload a PDF.
What does the server generally need to do?
A. Receive the uploaded data and save or process it
B. Turn the PDF into a monitor
C. Automatically install the PDF
D. Delete the HTTP request
Answer: A
A server may:
Browser
↓
Upload request
↓
Go server
↓
Validate file
↓
Save/process fileMCQ 15
Why should uploaded files be validated?
A. Users can submit unexpected or dangerous files
B. Validation makes the monitor brighter
C. Files cannot contain names
D. Go requires every file to be validated
Answer: A
A production application should consider:
file size
file type
file name
storage location
permissions
malicious content
authentication/authorization
Chapter 36 — Logging and Monitoring
MCQ 16
Why is logging useful?
A. It helps developers understand what happened while a program was running
B. It automatically fixes bugs
C. It replaces testing
D. It makes code shorter
Answer: A
For example:
log.Println("Starting payment processing")If something goes wrong later, logs can help you understand what the application was doing.
MCQ 17
Which information is usually useful in application logs?
A. Important events and errors
B. Every random thought of the programmer
C. User passwords
D. Secret API keys
Answer: A
Never put passwords, API keys, or other secrets into ordinary logs.
Chapter 37 — Advanced Testing and Benchmarks
MCQ 18
What is the main purpose of a unit test?
A. Check whether a small piece of code behaves as expected
B. Deploy the application
C. Create a database
D. Replace the compiler
Answer: A
Example:
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("expected 5, got %d", result)
}
}MCQ 19
What is a benchmark used for?
A. Measuring how efficiently code performs a particular operation
B. Checking spelling
C. Creating a user account
D. Encrypting passwords
Answer: A
Benchmarks are useful when investigating performance.
MCQ 20
What is a race detector useful for?
A. Finding certain data races in concurrent programs
B. Finding spelling mistakes
C. Compressing files
D. Creating APIs
Answer: A
Go provides the race detector through commands such as:
go test -race ./...Chapter 38 — Security, Secrets, and Dependencies
MCQ 21
Which is the safer approach for an API key?
A.
apiKey := "SECRET-12345"B. Store the secret outside the source code and provide it through a secure configuration mechanism
Answer: B
Putting secrets directly into source code can expose them through repositories, backups, logs, or shared code.
MCQ 22
Why should dependencies be managed carefully?
A. Third-party code can contain bugs or security problems and can affect your application
B. Dependencies always make programs slower
C. Go does not support packages
D. Dependencies are only useful for CSS
Answer: A
Use Go modules and keep dependencies under control.
Useful commands include:
go mod init example.com/myapp
go mod tidyMCQ 23
A user submits this:
../../important-file.txtWhy might blindly using this as a file path be dangerous?
A. It may allow access outside the intended directory
B. It is too short
C. Go cannot read .txt files
D. It automatically creates a database
Answer: A
This is related to path traversal.
Never trust file names or paths supplied by users.
Chapter 39 — Docker and Deployment
MCQ 24
Why use Docker?
A. To package an application and its environment in a consistent way
B. To replace Go
C. To create passwords
D. To increase monitor resolution
Answer: A
A simplified workflow is:
Go source code
↓
Build application
↓
Docker image
↓
Container
↓
ServerMCQ 25
What is deployment?
A. Making an application available in the environment where users can access it
B. Writing a comment
C. Renaming a variable
D. Closing a terminal
Answer: A
For example, moving a Go API from your computer to a cloud server is part of deployment.
Chapter 40 — Performance, Refactoring, and Maintenance
MCQ 26
What does refactoring mean?
A. Improving the structure of code without changing its intended behavior
B. Deleting the whole project
C. Changing Go into another language
D. Adding random features
Answer: A
Example:
Before:
total := price * quantity
discount := total * 10 / 100
final := total - discountAfter extracting the discount calculation:
func calculateDiscount(total float64) float64 {
return total * 10 / 100
}The purpose is cleaner and more maintainable code.
MCQ 27
What should you usually do before optimizing code?
A. Measure where the actual performance problem is
B. Rewrite everything
C. Add hundreds of goroutines
D. Delete tests
Answer: A
A common mistake is optimizing code that was never actually a bottleneck.
Measure first. Optimize second.
PART 2 — REAL-LIFE PROGRAMMING QUESTIONS
Now we move from recognizing concepts to actually programming.
Practical Question 1 — Run two tasks concurrently
Problem
Imagine your application needs to:
Download information.
Process another independent task.
Instead of waiting for the first task to completely finish before starting the second, run them as goroutines and wait for both to finish.
Folder
block4/
└── concurrency/
└── main.goSolution
package main
import (
"fmt"
"sync"
"time"
)
func work(name string, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println(name, "started")
time.Sleep(2 * time.Second)
fmt.Println(name, "finished")
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go work("Task 1", &wg)
go work("Task 2", &wg)
wg.Wait()
fmt.Println("All tasks completed")
}Important lines
var wg sync.WaitGroupCreates a waiting mechanism.
wg.Add(2)Tells Go that two tasks need to finish.
go work("Task 1", &wg)Starts the function as a goroutine.
defer wg.Done()Tells the WaitGroup that this task has finished.
wg.Wait()Waits until both tasks finish.
Practical lesson
You do not need concurrency everywhere.
Use it when independent work can safely happen concurrently and there is a practical reason to do so.
Practical Question 2 — Send data between goroutines
Problem
One goroutine calculates a result.
Another part of the program needs that result.
Use a channel to transfer the value.
Solution
package main
import "fmt"
func calculate(ch chan int) {
result := 100 + 200
ch <- result
}
func main() {
results := make(chan int)
go calculate(results)
result := <-results
fmt.Println("Result:", result)
}Understand the flow
main()
|
| starts
↓
calculate()
|
| calculates 300
↓
channel
|
↓
main()
|
↓
prints 300This is one of the fundamental patterns of Go concurrency:
goroutine → channel → another goroutine
Practical Question 3 — Add a timeout to an API request
Problem
Your application requests data from an external website.
What happens if that website takes 5 minutes to respond?
You don't want your program to wait forever.
Create a request with a timeout.
Solution
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
request, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://example.com",
nil,
)
if err != nil {
fmt.Println("Could not create request:", err)
return
}
response, err := http.DefaultClient.Do(request)
if err != nil {
fmt.Println("Request failed:", err)
return
}
defer response.Body.Close()
fmt.Println("Status:", response.Status)
}The important idea
context.WithTimeout(...)creates a deadline.
If the request takes too long, the context is cancelled.
This prevents external systems from holding your program indefinitely.
Practical Question 4 — Call an external API
Problem
Write a Go program that retrieves data from a public HTTP endpoint.
For a safe learning example, use:
https://api.github.comSolution
package main
import (
"fmt"
"net/http"
)
func main() {
response, err := http.Get("https://api.github.com")
if err != nil {
fmt.Println("Request failed:", err)
return
}
defer response.Body.Close()
fmt.Println("HTTP status:", response.Status)
}Example output:
HTTP status: 200 OKThe exact status depends on whether the service is available when you run it.
What you learned
Go program
↓
HTTP GET request
↓
External server
↓
HTTP response
↓
Go programThis is the foundation of API integration.
Practical Question 5 — Read JSON from an API
Suppose an API returns:
{
"name": "Pramod",
"age": 30
}Create a Go structure that represents this data.
Solution
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
data := []byte(`{
"name": "Pramod",
"age": 30
}`)
var user User
err := json.Unmarshal(data, &user)
if err != nil {
fmt.Println("Invalid JSON:", err)
return
}
fmt.Println("Name:", user.Name)
fmt.Println("Age:", user.Age)
}Key idea
JSON:
"name" → Name
"age" → AgeThe struct tells Go what shape the data has.
json.Unmarshal() converts JSON data into Go values.
Practical Question 6 — Build a file upload endpoint
Problem
Create a simple HTTP server that accepts a file upload.
Folder
block4/
└── upload/
└── main.goSolution
package main
import (
"fmt"
"net/http"
"os"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "File upload failed", http.StatusBadRequest)
return
}
defer file.Close()
output, err := os.Create("uploaded-" + header.Filename)
if err != nil {
http.Error(w, "Could not save file", http.StatusInternalServerError)
return
}
defer output.Close()
buffer := make([]byte, 32*1024)
_, err = file.Read(buffer)
if err != nil {
http.Error(w, "Could not read file", http.StatusInternalServerError)
return
}
fmt.Fprintln(w, "Upload received")
}
func main() {
http.HandleFunc("/upload", uploadHandler)
fmt.Println("Server running on http://localhost:8080")
http.ListenAndServe(":8080", nil)
}Important correction
The above deliberately demonstrates the upload concept but is not a production-safe file-saving implementation: it does not actually copy the complete file to disk, and directly using header.Filename needs validation.
A better production pattern uses io.Copy, validates the file size/type, and generates a safe server-side filename.
Example core operation:
_, err = io.Copy(output, file)This copies the complete uploaded file.
Real-world lesson
File uploads require more than simply receiving bytes.
You must think about:
maximum size
allowed types
filenames
storage location
permissions
malicious files
authentication
Practical Question 7 — Add useful logging
Problem
Imagine your API receives an order.
You want logs showing when the order starts and whether it succeeded.
Solution
package main
import (
"log"
)
func processOrder(orderID int) {
log.Printf("Starting order %d", orderID)
// Imagine order processing happens here.
log.Printf("Order %d completed", orderID)
}
func main() {
processOrder(101)
}Possible output:
2026/10/03 02:30:00 Starting order 101
2026/10/03 02:30:00 Order 101 completedThe timestamp will depend on your machine.
Important rule
Never do this:
log.Println("Password:", password)or:
log.Println("API key:", apiKey)Logs can be stored, copied, or accessed by other people.
Practical Question 8 — Write a proper test
Problem
Create a function that adds two numbers and test it automatically.
Folder
block4/
└── testing/
├── calculator.go
└── calculator_test.gocalculator.go
package testing
func Add(a int, b int) int {
return a + b
}calculator_test.go
package testing
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("expected 5, got %d", result)
}
}Run:
go testExpected result:
PASS
okWhy this matters
Without a test:
Change code
↓
Run manually
↓
Check resultWith tests:
Change code
↓
go test
↓
Automatic checkingAs projects become larger, automated testing saves enormous amounts of time.
Practical Question 9 — Keep secrets outside the code
Bad approach
apiKey := "abc123-secret-key"If you upload this project to GitHub, you may accidentally expose the key.
Better approach
Set an environment variable.
For example:
export API_KEY="your-secret-key"Then:
package main
import (
"fmt"
"os"
)
func main() {
apiKey := os.Getenv("API_KEY")
if apiKey == "" {
fmt.Println("API_KEY is not configured")
return
}
fmt.Println("API key is configured")
}Notice that the program does not print the actual secret.
On Windows PowerShell:
$env:API_KEY="your-secret-key"Practical lesson
Source code:
program logicEnvironment/configuration:
secret valuesKeep them separate.
Practical Question 10 — Find a performance problem
Consider this code:
package main
import "fmt"
func main() {
numbers := []int{1, 2, 3, 4, 5}
for i := 0; i < len(numbers); i++ {
fmt.Println(numbers[i])
}
}Is this code necessarily a performance problem?
Answer: No.
There is no reason to optimize it just because another loop style might look cleaner.
The important professional habit is:
Problem
↓
Measure
↓
Find bottleneck
↓
Change code
↓
Measure againDo not blindly optimize everything.
PART 3 — ONE COMBINED PROJECT
Build a Concurrent API Monitor
This project combines:
goroutines
channels
HTTP
timeouts
error handling
logging
functions
slices
testing concepts
performance thinking
Folder structure
api-monitor/
├── go.mod
├── main.go
└── monitor.gogo.mod
module api-monitor
go 1.23Use the Go version installed on your computer if it differs.
monitor.go
package main
import (
"context"
"net/http"
"time"
)
type Result struct {
URL string
Status string
Duration time.Duration
Error error
}
func checkURL(ctx context.Context, url string) Result {
start := time.Now()
request, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
url,
nil,
)
if err != nil {
return Result{
URL: url,
Error: err,
}
}
response, err := http.DefaultClient.Do(request)
duration := time.Since(start)
if err != nil {
return Result{
URL: url,
Duration: duration,
Error: err,
}
}
defer response.Body.Close()
return Result{
URL: url,
Status: response.Status,
Duration: duration,
}
}main.go
package main
import (
"context"
"fmt"
"log"
"sync"
"time"
)
func main() {
urls := []string{
"https://example.com",
"https://api.github.com",
"https://go.dev",
}
results := make(chan Result)
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
ctx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
log.Println("Checking:", url)
result := checkURL(ctx, url)
results <- result
}(url)
}
go func() {
wg.Wait()
close(results)
}()
for result := range results {
if result.Error != nil {
fmt.Println(
result.URL,
"ERROR:",
result.Error,
)
continue
}
fmt.Println(
result.URL,
result.Status,
result.Duration,
)
}
fmt.Println("Finished.")
}Run it
Inside the project folder:
go mod tidy
go run .The exact response times and status values will depend on network conditions and the services being available.
Understand the architecture
┌── example.com
│
Go Program ───────┼── api.github.com
│
└── go.dev
↓
concurrent requests
↓
channel
↓
results
↓
outputWhy use goroutines?
The three websites don't need to be checked one after another.
We can start their checks concurrently.
Why use a channel?
Each goroutine needs a safe way to send its result back.
results <- resultWhy use WaitGroup?
We need to know when every goroutine has completed.
Why use context.WithTimeout?
A website shouldn't be allowed to make our program wait forever.
Why use Result?
Instead of returning several unrelated values, we create one object containing:
URL
Status
Duration
ErrorThis makes the result easier to work with.
PART 4 — FINAL PRACTICAL CHALLENGES
Challenge 1 — Add response status checking
Change the monitor so that:
200–299 → SUCCESS
400–499 → CLIENT ERROR
500–599 → SERVER ERRORSolution
Add a function:
func classifyStatus(code int) string {
switch {
case code >= 200 && code <= 299:
return "SUCCESS"
case code >= 400 && code <= 499:
return "CLIENT ERROR"
case code >= 500 && code <= 599:
return "SERVER ERROR"
default:
return "OTHER"
}
}Then use:
fmt.Println(
result.URL,
result.Status,
classifyStatus(responseCode),
)Challenge 2 — Add a maximum number of concurrent requests
Suppose you have 10,000 URLs.
Starting 10,000 goroutines at once may be unnecessary.
A practical solution is to limit concurrency.
For example:
10,000 URLs
↓
worker pool
↓
20 workers
↓
process URLs graduallyThis is an important real-world concurrency pattern.
Challenge 3 — Add tests
Test:
classifyStatus(200)Expected:
SUCCESSTest:
classifyStatus(404)Expected:
CLIENT ERRORTest:
classifyStatus(500)Expected:
SERVER ERRORExample:
func TestClassifyStatus(t *testing.T) {
tests := []struct {
code int
expected string
}{
{200, "SUCCESS"},
{404, "CLIENT ERROR"},
{500, "SERVER ERROR"},
}
for _, test := range tests {
result := classifyStatus(test.code)
if result != test.expected {
t.Errorf(
"expected %s, got %s",
test.expected,
result,
)
}
}
}BLOCK 4 — The 80/20 You Actually Need to Remember
You do not need to memorize this entire chapter.
The most reusable ideas are these:
Goroutine
↓
Run independent work concurrently
Channel
↓
Move data between goroutines
WaitGroup
↓
Wait for goroutines to finish
Context
↓
Timeout / cancel work
HTTP
↓
Communicate with other systems
JSON
↓
Exchange structured data
Logging
↓
Understand what happened
Testing
↓
Automatically verify code
Environment variables
↓
Keep configuration/secrets outside source code
Docker
↓
Package application consistently
Profiling / measurement
↓
Find real performance problems
Refactoring
↓
Improve code structure without changing its intended behaviorThe real professional workflow
Write code
↓
Run it
↓
Test it
↓
Handle errors
↓
Log important events
↓
Secure secrets
↓
Measure performance
↓
Improve/refactor
↓
Deploy
↓
MonitorThat is much more valuable than memorizing dozens of isolated Go features.
Block 4's core transition is: you move from “I can write Go programs” to “I can make a Go program communicate with other systems, perform concurrent work, handle failures, test itself, and be prepared for deployment.”
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.