Go Programming — Block 4: Professional Skills and Efficient Programming

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:

  1. Goroutines and Concurrency

  2. Channels and Synchronization

  3. Context, Timeouts, and Cancellation

  4. Networking and External APIs

  5. File Uploads and Downloads

  6. Logging and Application Monitoring

  7. Advanced Testing and Benchmarks

  8. Security, Secrets, and Dependency Management

  9. Docker and Deployment Workflows

  10. 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 := <-messages

A. 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 program

MCQ 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 — retrieve

  • POST — create/send data

  • PUT — replace/update

  • PATCH — partially update

  • DELETE — 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 file

MCQ 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 tidy

MCQ 23

A user submits this:

../../important-file.txt

Why 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
      ↓
Server

MCQ 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 - discount

After 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:

  1. Download information.

  2. 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.go

Solution

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.WaitGroup

Creates 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 300

This 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.com

Solution

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 OK

The 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 program

This 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"  → Age

The 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.go

Solution

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 completed

The 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.go

calculator.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 test

Expected result:

PASS
ok

Why this matters

Without a test:

Change code
↓
Run manually
↓
Check result

With tests:

Change code
↓
go test
↓
Automatic checking

As 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 logic

Environment/configuration:

secret values

Keep 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 again

Do 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.go

go.mod

module api-monitor

go 1.23

Use 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
                       ↓
                     output

Why 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 <- result

Why 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
Error

This 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 ERROR

Solution

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 gradually

This is an important real-world concurrency pattern.


Challenge 3 — Add tests

Test:

classifyStatus(200)

Expected:

SUCCESS

Test:

classifyStatus(404)

Expected:

CLIENT ERROR

Test:

classifyStatus(500)

Expected:

SERVER ERROR

Example:

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 behavior

The real professional workflow

Write code
    ↓
Run it
    ↓
Test it
    ↓
Handle errors
    ↓
Log important events
    ↓
Secure secrets
    ↓
Measure performance
    ↓
Improve/refactor
    ↓
Deploy
    ↓
Monitor

That 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.