Go Programming — Block 5: Advanced Skills and Specialization

Yes. For Block 5, I’ll keep the same learning philosophy, but make it the advanced/practical block. Since you’ll paste it directly into a Blogspot post, every MCQ and practical question will have its answer + explanation/solution included.

Go Programming — Block 5

Specialization, Advanced Go & Complete Application Architecture

Chapters covered:

  1. Generics

  2. Reflection

  3. Advanced Interfaces & Design Patterns

  4. Advanced SQL & Database Transactions

  5. Caching

  6. Background Jobs & Task Queues

  7. WebSockets & Real-Time Communication

  8. Profiling & Memory Optimization

  9. CI/CD & Automated Deployment

  10. Architecture & Capstone Project


Part 1 — High-Value MCQs

Chapter 41 — Generics

MCQ 1

Suppose you want one function that can find the larger value for both integers and decimal numbers. What Go feature is designed for this?

A. Reflection
B. Generics
C. Goroutines
D. Channels

Answer: B — Generics

Why: Generics allow you to write reusable code that can work with different data types without writing a separate function for every type.


MCQ 2

What is the main benefit of generics?

A. They automatically make programs faster.
B. They allow reusable code to work with multiple types.
C. They eliminate all errors.
D. They replace databases.

Answer: B

Simple idea: Instead of writing:

findMaximumInt()
findMaximumFloat()
findMaximumString()

you can sometimes create one generic function that works with several appropriate types.


MCQ 3

Which situation is a good reason to consider generics?

A. You need to print "Hello".
B. You have several functions doing essentially the same operation for different types.
C. You need to start a web server.
D. You need to connect to MySQL.

Answer: B

Generics are mainly about reusing algorithms across types.


Chapter 42 — Reflection

MCQ 4

What does reflection allow a Go program to do?

A. Examine information about values and types at runtime.
B. Automatically fix every bug.
C. Increase CPU speed.
D. Replace the compiler.

Answer: A

Reflection lets a program inspect things such as a value's type while the program is running.


MCQ 5

Why should reflection generally not be your first choice?

A. It cannot work in Go.
B. It can make code harder to understand and maintain.
C. It only works with strings.
D. It requires a database.

Answer: B

For ordinary programming, direct types, structs, interfaces, and generics are usually easier to understand.

80/20 rule: Learn what reflection is and recognize when it exists. Don't spend a large amount of time mastering it unless your project actually needs it.


Chapter 43 — Advanced Interfaces & Design

MCQ 6

Suppose your application can send notifications through email, SMS, or another service. What can an interface help you do?

A. Make all services identical internally.
B. Allow different implementations to provide the same required behavior.
C. Remove the need for functions.
D. Automatically create a database.

Answer: B

For example, your application might depend on:

type Notifier interface {
    Send(message string) error
}

An email notifier and an SMS notifier can implement Send() differently while the rest of the application works with the Notifier interface.


MCQ 7

What is the practical advantage of this design?

A. You can change the implementation with less impact on the rest of the program.
B. The application never needs testing.
C. The program becomes automatically distributed.
D. Interfaces store database records.

Answer: A

This is particularly useful when replacing an external service or creating test versions of services.


Chapter 44 — Advanced SQL & Transactions

MCQ 8

Suppose a bank transfer involves:

  1. Removing ₹1,000 from Account A.

  2. Adding ₹1,000 to Account B.

Why might these operations need a database transaction?

A. Both operations should succeed together or be rolled back together.
B. Transactions make the screen prettier.
C. Transactions replace SQL.
D. Transactions make passwords unnecessary.

Answer: A

You don't want money removed from A while the addition to B fails.


MCQ 9

What does COMMIT generally mean?

A. Permanently accept the successful operations in the transaction.
B. Delete the database.
C. Start a new programming language.
D. Create a Go package.

Answer: A


MCQ 10

What does ROLLBACK generally do?

A. Reverses the uncommitted operations of a transaction.
B. Deletes Go source code.
C. Restarts the computer.
D. Compresses the database.

Answer: A


Chapter 45 — Caching

MCQ 11

Your website repeatedly asks the database for the same popular product information. What can caching help with?

A. Avoiding unnecessary repeated work.
B. Increasing the number of database queries.
C. Removing all application code.
D. Replacing the operating system.

Answer: A

A cache stores frequently needed information temporarily so it can potentially be retrieved faster.


MCQ 12

What is one major danger of caching?

A. Cached information can become outdated.
B. Cache always destroys databases.
C. Cache cannot store data.
D. Cache only works with images.

Answer: A

This is called stale data.

For example:

Database: Product price = ₹500
Cache:    Product price = ₹450

If the cache isn't updated or expired correctly, users may see the old price.


Chapter 46 — Background Jobs

MCQ 13

A website receives an order. Sending a confirmation email takes several seconds. What is one possible reason to process the email as a background job?

A. The user doesn't necessarily need to wait for the email operation to finish before the order response is returned.
B. Emails cannot be sent by programs.
C. Background jobs eliminate the database.
D. It makes the email free.

Answer: A

Conceptually:

User
  ↓
Place Order
  ↓
Save Order
  ↓
Respond Quickly
  ↓
Background Worker
  ↓
Send Email

MCQ 14

What happens if a background job fails?

A. A properly designed system can retry it or record the failure for later handling.
B. The entire internet stops.
C. Go automatically fixes it.
D. The database must always be deleted.

Answer: A


Chapter 47 — WebSockets

MCQ 15

A normal HTTP request often follows:

Client → Request → Server → Response

Why might WebSockets be useful for a live chat application?

A. They allow ongoing two-way communication between client and server.
B. They only work for databases.
C. They prevent all network failures.
D. They replace HTML.

Answer: A


MCQ 16

Which application could benefit from real-time communication?

A. Live chat
B. Live notifications
C. Real-time dashboard
D. All of the above

Answer: D


Chapter 48 — Profiling & Optimization

MCQ 17

Your Go application feels slow. What should you generally do before rewriting large amounts of code?

A. Measure and identify where the program is actually spending time or memory.
B. Rewrite everything immediately.
C. Add random goroutines.
D. Delete error handling.

Answer: A

This is the basic principle:

Measure first → identify the bottleneck → improve it → measure again.


MCQ 18

What is a performance bottleneck?

A. A part of a program that significantly limits overall performance.
B. A Go keyword.
C. A database password.
D. A type of variable.

Answer: A


Chapter 49 — CI/CD

MCQ 19

What is the purpose of CI?

A. Automatically build and test code when changes are submitted.
B. Automatically write all source code.
C. Replace Git.
D. Store passwords in source files.

Answer: A


MCQ 20

What is a typical deployment pipeline?

Developer
   ↓
Git
   ↓
Build
   ↓
Tests
   ↓
Deploy

Why is this useful?

A. It reduces repetitive manual deployment work and catches problems earlier.
B. It guarantees that every program is bug-free.
C. It eliminates the need for testing.
D. It removes the need for source control.

Answer: A


Chapter 50 — Architecture

MCQ 21

A large application becomes difficult to maintain because database code, business rules, HTTP handling, and email code are all mixed together.

What is one sensible improvement?

A. Separate responsibilities into appropriate components.
B. Put everything into one giant function.
C. Remove all functions.
D. Duplicate the code.

Answer: A


MCQ 22

Why is separation of responsibilities useful?

A. Changing one part is less likely to unnecessarily affect unrelated parts.
B. It guarantees zero bugs.
C. It eliminates the need for programmers.
D. It makes every program smaller.

Answer: A


Part 2 — Real-Life Programming Problems

Now we move from knowing the concept to actually using it.


Problem 1 — Generic maximum function

Situation

You want one function that can determine the larger value instead of creating separate functions for different numeric types.

Solution

package main

import "fmt"

func Max[T int | float64](a, b T) T {
    if a > b {
        return a
    }

    return b
}

func main() {
    fmt.Println(Max(10, 20))
    fmt.Println(Max(10.5, 7.2))
}

Output

20
10.5

What happened?

[T int | float64]

means the function can work with either int or float64.

The same function can therefore be reused for both:

Max(10, 20)

and:

Max(10.5, 7.2)

Practical lesson

Don't use generics simply because they exist.

Use them when they remove meaningful duplication.


Problem 2 — Multiple notification systems

Situation

Your application currently sends email notifications.

Later, you want SMS notifications too.

Instead of changing the entire application, create a common interface.

Folder structure

notification-app/
├── main.go
└── notification.go

notification.go

package main

import "fmt"

type Notifier interface {
    Send(message string)
}

type EmailNotifier struct{}

func (EmailNotifier) Send(message string) {
    fmt.Println("Sending email:", message)
}

type SMSNotifier struct{}

func (SMSNotifier) Send(message string) {
    fmt.Println("Sending SMS:", message)
}

func NotifyUser(n Notifier, message string) {
    n.Send(message)
}

main.go

package main

func main() {
    email := EmailNotifier{}
    sms := SMSNotifier{}

    NotifyUser(email, "Your order has been shipped.")
    NotifyUser(sms, "Your order has been shipped.")
}

Output

Sending email: Your order has been shipped.
Sending SMS: Your order has been shipped.

The important idea

NotifyUser() doesn't care whether the notification is email or SMS.

It only knows:

Notifier

and that a Notifier can:

Send(message string)

That makes the system easier to extend.


Problem 3 — Safe bank transfer

Situation

Transfer ₹1,000 from one bank account to another.

You don't want this:

Subtract ₹1,000 from A
        ↓
ERROR
        ↓
Nothing added to B

Instead:

START TRANSACTION
       ↓
Subtract from A
       ↓
Add to B
       ↓
Everything successful?
    ↙       ↘
  YES        NO
   ↓          ↓
COMMIT     ROLLBACK

Simplified Go example

tx, err := db.Begin()

if err != nil {
    return err
}

_, err = tx.Exec(
    "UPDATE accounts SET balance = balance - ? WHERE id = ?",
    1000,
    accountA,
)

if err != nil {
    tx.Rollback()
    return err
}

_, err = tx.Exec(
    "UPDATE accounts SET balance = balance + ? WHERE id = ?",
    1000,
    accountB,
)

if err != nil {
    tx.Rollback()
    return err
}

return tx.Commit()

What this teaches

The transaction creates a protected group of database operations.

If something fails:

tx.Rollback()

If everything succeeds:

tx.Commit()

Important: A real banking system requires much more than this simplified example, including proper authorization, concurrency handling, database constraints, auditing, and careful money representation.


Problem 4 — Simple cache

Situation

Your application repeatedly calculates the same expensive result.

Instead of calculating it every time, keep the result temporarily.

package main

import "fmt"

var cache = map[string]int{}

func expensiveCalculation(key string) int {
    if value, exists := cache[key]; exists {
        fmt.Println("Using cached value")
        return value
    }

    fmt.Println("Calculating...")
    
    result := len(key) * 100

    cache[key] = result

    return result
}

func main() {
    fmt.Println(expensiveCalculation("Go"))
    fmt.Println(expensiveCalculation("Go"))
}

Output:

Calculating...
200
Using cached value
200

What happened?

First call:

No cached result
      ↓
Calculate
      ↓
Store result

Second call:

Find result in cache
      ↓
Return it

Real-world examples

Caching can be used for:

  • Popular product information.

  • Frequently requested API data.

  • Configuration.

  • Expensive calculations.

  • Frequently accessed database records.

But you must decide when cached information expires or gets updated.


Problem 5 — Background order processing

Situation

A customer places an order.

The application needs to:

  1. Save the order.

  2. Respond to the customer quickly.

  3. Send an email afterward.

Conceptually:

Customer
   ↓
Order API
   ↓
Save order
   ↓
Return success
   ↓
Background job
   ↓
Send email

A simplified Go demonstration:

package main

import (
    "fmt"
    "time"
)

func sendEmail() {
    fmt.Println("Sending email...")
    time.Sleep(2 * time.Second)
    fmt.Println("Email sent")
}

func main() {
    fmt.Println("Order saved")
    fmt.Println("Order confirmed")

    go sendEmail()

    time.Sleep(3 * time.Second)
}

Important

This demonstrates the idea of a goroutine, but a production background-job system needs proper job storage, retries, failure handling, graceful shutdown, and monitoring.

The important architectural idea is:

Don't make the user wait for work that can safely happen afterward.


Problem 6 — Real-time notification

Situation

Imagine a dashboard showing live order updates.

Instead of constantly asking:

"Anything new?"
"Anything new?"
"Anything new?"
"Anything new?"

a persistent WebSocket connection can allow the server to push an update when something happens.

Conceptually:

Browser
   ↕
WebSocket connection
   ↕
Go server
   ↓
New order
   ↓
Push notification
   ↓
Browser updates immediately

The key thing to learn here is when real-time communication is actually useful.

You don't need WebSockets for a normal page that only needs information when the user opens it.


Problem 7 — Find a performance bottleneck

Imagine this function:

func processOrders(orders []Order) {
    for _, order := range orders {
        calculateTax(order)
        saveOrder(order)
        sendEmail(order)
    }
}

Suppose processing 10,000 orders takes 30 minutes.

Don't immediately rewrite everything.

Ask:

Where is the time actually going?

Perhaps:

calculateTax → 1 second
saveOrder    → 28 minutes
sendEmail    → 1 minute

Now the database operation is the obvious area to investigate.

The 80/20 lesson

Optimization is not guessing.

It is:

Measure
  ↓
Find bottleneck
  ↓
Understand cause
  ↓
Change code
  ↓
Measure again

Problem 8 — Automated testing and deployment

Imagine your Git repository receives a new change.

Instead of manually doing everything:

Pull code
↓
Build
↓
Run tests
↓
Deploy

you can automate the process.

Developer pushes code
        ↓
       Git
        ↓
   Automated build
        ↓
   Automated tests
        ↓
    Tests pass?
      ↙     ↘
    YES      NO
     ↓        ↓
  Deploy    Stop

Practical benefit

If someone introduces a change that breaks an existing test, the pipeline can detect it before deployment.


Part 3 — Capstone Project

Now combine the most useful Block 5 concepts into one realistic application.

Project — Mini E-Commerce Backend

We will build a simplified Go backend capable of:

  • Managing products.

  • Creating orders.

  • Storing data in a database.

  • Using transactions.

  • Caching frequently accessed products.

  • Sending background notifications.

  • Providing an API.

  • Structuring the application cleanly.

  • Testing important business logic.

  • Preparing it for automated deployment.

Folder structure

shop-api/
├── cmd/
│   └── server/
│       └── main.go
│
├── internal/
│   ├── product/
│   │   ├── model.go
│   │   ├── repository.go
│   │   └── service.go
│   │
│   ├── order/
│   │   ├── model.go
│   │   ├── repository.go
│   │   └── service.go
│   │
│   ├── notification/
│   │   └── notification.go
│   │
│   └── cache/
│       └── cache.go
│
├── migrations/
│   └── 001_initial.sql
│
├── tests/
│   └── product_test.go
│
├── go.mod
└── README.md

Why this structure?

Instead of putting everything inside:

main.go

we separate responsibilities.

product/
    Product-related code

order/
    Order-related code

notification/
    Notification-related code

cache/
    Caching-related code

This becomes increasingly useful as an application grows.


The architecture

Think of the application like a restaurant.

                 USER
                   ↓
              HTTP API
                   ↓
                SERVICE
                   ↓
             BUSINESS RULES
              ↙          ↘
        DATABASE        CACHE
              ↓
       Background Jobs
              ↓
        Notifications

API layer

Receives requests.

Example:

POST /orders

Service layer

Decides what should happen.

Example:

Is the product available?
Is the quantity valid?
What is the total?

Repository layer

Deals with database operations.

Cache

Provides frequently requested information quickly.

Background worker

Performs work that doesn't need to block the user's request.


Capstone Challenge 1

A customer requests:

GET /products/10

Explain the flow.

Answer

Browser
   ↓
HTTP request
   ↓
Go API
   ↓
Product service
   ↓
Check cache
   ↓
Found?
 ↙       ↘
YES       NO
 ↓         ↓
Return   Database
           ↓
        Store in cache
           ↓
         Return

The important lesson is that the user doesn't need to know how the application obtains the data.


Capstone Challenge 2

A customer purchases two products.

What should happen?

Answer

A sensible simplified flow is:

POST /orders
      ↓
Validate request
      ↓
Start database transaction
      ↓
Check products
      ↓
Check stock
      ↓
Create order
      ↓
Reduce stock
      ↓
Commit transaction
      ↓
Return order confirmation
      ↓
Background job
      ↓
Send confirmation notification

If creating the order succeeds but reducing stock fails:

ROLLBACK

The incomplete transaction should not be committed.


Capstone Challenge 3

The product page receives 100,000 requests for the same product.

The database is becoming overloaded.

What could you investigate?

Answer

A cache could store frequently requested product information.

Request
   ↓
Cache?
 ↙     ↘
YES     NO
 ↓       ↓
Return Database
          ↓
        Cache
          ↓
        Return

But you must also design cache expiration/invalidation so that important product changes don't leave users seeing stale information indefinitely.


Capstone Challenge 4

Your application works perfectly on your computer but fails after deployment.

What should your development process have done to catch problems earlier?

Answer

Use automated:

Build
 ↓
Tests
 ↓
Deployment checks

and ideally reproduce the production environment as closely as practical.

The key lesson is:

"It works on my computer" is not a deployment strategy.


Part 4 — Block 5 Final Skill Test

Question 1

You have the same algorithm repeated for int, float64, and another compatible type.

Which feature should you investigate first?

Answer

Generics.

They can allow one reusable implementation instead of multiple versions when the operation is genuinely type-independent.


Question 2

You need to inspect the type of an unknown value while the program is running.

Answer

Reflection can do this.

But first ask whether a normal type, interface, or generic design can solve the problem more clearly.


Question 3

A payment consists of several database operations that must succeed together.

Answer

Use a database transaction.

BEGIN
 ↓
Operation 1
 ↓
Operation 2
 ↓
Everything OK?
 ↙       ↘
YES       NO
 ↓         ↓
COMMIT   ROLLBACK

Question 4

A user doesn't need to wait for a report-generation task to finish.

Answer

Consider processing it as a background job.


Question 5

A chat application needs instant two-way updates.

Answer

WebSockets are one suitable technology.


Question 6

An application is slow.

Should you immediately rewrite the slow-looking function?

Answer

No.

First measure the application and identify the actual bottleneck.


Question 7

Your application contains:

HTTP code
database code
business rules
email code

all inside one enormous main.go.

Answer

Separate responsibilities into sensible components/packages.

This makes the application easier to understand, test, replace, and maintain.


The Block 5 80/20 takeaway

You do not need to master all advanced Go features before becoming productive.

The highest-value mental model is:

GENERICS
   ↓
Reusable code

INTERFACES
   ↓
Replaceable components

DATABASE TRANSACTIONS
   ↓
Safe multi-step operations

CACHE
   ↓
Avoid unnecessary repeated work

BACKGROUND JOBS
   ↓
Move non-urgent work away from user requests

WEBSOCKETS
   ↓
Real-time communication

PROFILING
   ↓
Find actual bottlenecks

CI/CD
   ↓
Automated build → test → deployment

ARCHITECTURE
   ↓
Keep large applications understandable

And one final principle ties the entire five-block course together:

Don't learn a programming feature because it exists. Learn it because you have a problem that the feature solves.

By the end of Block 5, the objective isn't that you can recite Go's entire language specification. The objective is that when someone gives you a real software problem, you can break it into smaller problems, choose the appropriate Go concepts, create the files, write the code, run it, debug it, test it, and gradually turn it into a maintainable application.

No comments:

Post a Comment

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