This repository exists to teach how Go thinks about concurrency, not just how to write code that happens to work.
This repository is a minimal, production-style Go project designed to teach the core concurrency primitives of the language:
- Channels for communication and backpressure
- Goroutines for concurrent execution
- context.Context for cancellation and lifecycle management
- Explicit composition instead of frameworks
The project intentionally avoids databases, ORMs, and third‑party libraries so you can focus entirely on how Go coordinates work.
At a high level, the system works like this:
- An HTTP endpoint (
/log) receives requests - Each request produces a unit of work
- Work is sent through a channel (no shared memory)
- A pool of workers processes work concurrently
- Results are aggregated and logged
- A single cancellation signal shuts everything down cleanly
This mirrors real-world Go services used in:
- APIs
- background job processors
- event pipelines
- message consumers
go run .In another terminal:
curl http://localhost:8080/logPress Ctrl+C to observe graceful shutdown.
Locks protect memory. Channels protect behavior.
Locks answer the question:
“Who can access this data right now?”
Channels answer the question:
“Who owns this work now?”
When you send a value on a channel:
jobs <- jobYou are saying:
“I am done with this value. Someone else owns it now.”
This eliminates:
- Shared mutable state
- Lock ordering bugs
- Deadlocks caused by forgotten mutexes
Rule of thumb:
Don’t communicate by sharing memory; share memory by communicating.
Go assumes cooperative cancellation, not forceful termination.
Force-killing goroutines causes:
- Partial work
- Resource leaks
- Corrupted state
- Unpredictable shutdowns
Instead, workers listen for cancellation signals:
select {
case <-ctx.Done():
return
case job := <-jobs:
process(job)
}This means:
- Work finishes at safe boundaries
- Goroutines exit cleanly
- Shutdown is predictable
Rule of thumb:
Goroutines should stop because they are told to stop — not because they are killed.
context.Context is request-scoped.
It represents:
- Cancellation
- Deadlines
- Request metadata
It is not:
- Global state
- Configuration
- A service locator
Correct usage:
func handle(ctx context.Context, job Job) errorIncorrect usage:
var ctx context.Context // ❌Contexts:
- Flow downward through function calls
- Are cancelled by parents
- Should never outlive the request
Rule of thumb:
If a function does I/O or starts goroutines, it should accept a context.
Sending a value:
jobs <- jobMeans:
“Here is one more piece of work.”
Closing a channel:
close(jobs)Means:
“There will never be more work.”
This allows consumers to write:
for job := range jobs {
process(job)
}No special values. No magic flags. No protocol pollution.
Rule of thumb:
Closing a channel communicates lifecycle, not information.