Go/
Lesson

Go's concurrencyWhat is concurrency?The ability of a program to handle multiple tasks at the same time, like serving thousands of users without slowing down. model is built on CSP (Communicating Sequential Processes), the idea that goroutines should coordinate by passing messages, not by sharing memory. Channels are those message pipes. They're type-safe, goroutineWhat is goroutine?A lightweight concurrent function in Go launched with the go keyword - much cheaper than an OS thread (~2KB vs ~1MB of stack).-safe, and the primary way concurrent Go code communicates.

AI generates channelWhat is channel?A typed conduit in Go used to pass values between goroutines - can be unbuffered (synchronous) or buffered (async queue). code prolifically, and gets it wrong in subtle ways that compile fine but deadlockWhat is deadlock?A situation where two or more operations are stuck waiting on each other forever, so none of them can proceed. at runtimeWhat is runtime?The environment that runs your code after it's written. Some languages need a runtime installed on the machine; others (like Go) bake it into the binary..

ChannelWhat is channel?A typed conduit in Go used to pass values between goroutines - can be unbuffered (synchronous) or buffered (async queue). basics

A channel is a typed conduit. You create it with make, send with <-, and receive with <-:

ch := make(chan string)    // unbuffered channel of strings

go func() {
    ch <- "hello"          // send - blocks until someone receives
}()

msg := <-ch                // receive - blocks until someone sends
fmt.Println(msg)           // "hello"

The arrow direction tells you everything: ch <- value pushes into the channel, <-ch pulls out.

OperationSyntaxBlocks when
Create unbufferedmake(chan T)N/A
Create bufferedmake(chan T, n)N/A
Sendch <- valueBuffer full (or unbuffered and no receiver)
Receivev := <-chBuffer empty (or unbuffered and no sender)
Receive + checkv, ok := <-chBuffer empty and channel open
Closeclose(ch)N/A (never blocks)
02

Unbuffered vs buffered: the critical distinction

This is where AI fails most often. The difference isn't just performance, it fundamentally changes program behavior.

Unbuffered channels are synchronous rendezvous points. The sender blocks until a receiver is ready, and vice versa. Both goroutines must arrive at the channelWhat is channel?A typed conduit in Go used to pass values between goroutines - can be unbuffered (synchronous) or buffered (async queue). operation at roughly the same time:

ch := make(chan int) // unbuffered

// This deadlocks - no one is receiving!
ch <- 42             // blocks forever in main goroutine
fmt.Println(<-ch)

Buffered channels decouple sender and receiver. The sender only blocks when the buffer is full:

ch := make(chan int, 3) // buffer of 3

ch <- 1  // doesn't block - buffer has space
ch <- 2  // doesn't block
ch <- 3  // doesn't block
// ch <- 4 // WOULD block - buffer full

fmt.Println(<-ch) // 1 (FIFO)
AspectUnbufferedBuffered
SynchronizationSender and receiver synchronizedDecoupled up to buffer size
Deadlock riskHigh if used in same goroutineLower but still possible
BackpressureImmediate, sender feels it instantlyDelayed, only when buffer fills
Use caseHandoffs, signaling, synchronizationWork queues, rate smoothing
AI defaultRarely chosen correctlyOften over-buffered to "fix" deadlocks
AI pitfall
When AI encounters a deadlock with an unbuffered channel, its fix is usually "make it buffered." This hides the synchronization bug instead of fixing it. A buffer of 1 doesn't fix a fundamental design problem where a goroutine can't make progress, it just delays the deadlock until the buffer fills.
03

ChannelWhat is channel?A typed conduit in Go used to pass values between goroutines - can be unbuffered (synchronous) or buffered (async queue). directions in function signatures

Go lets you restrict a channel parameter to send-only or receive-only. This is documentation and compile-time safety:

func producer(out chan<- int) {   // can only send
    out <- 42
    // <-out  // compile error
}

func consumer(in <-chan int) {    // can only receive
    val := <-in
    // in <- 1  // compile error
    fmt.Println(val)
}

When you ask AI to write a pipelineWhat is pipeline?A sequence of automated steps (install, lint, test, build, deploy) that code passes through before reaching production. function, check that it uses directional channels. If all channels are bidirectional chan T, the code works but loses compile-time safety against accidental misuse.

AI pitfall
AI often returns chan T (bidirectional) from functions that should return <-chan T (receive-only). This leaks control, callers can close or send on a channel they shouldn't touch.
04

Closing channels

Closing a channelWhat is channel?A typed conduit in Go used to pass values between goroutines - can be unbuffered (synchronous) or buffered (async queue). signals that no more values will be sent. Receivers can detect this:

ch := make(chan int)

go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch) // signal: no more values
}()

// range automatically stops when channel is closed
for v := range ch {
    fmt.Println(v) // 0, 1, 2, 3, 4
}

The rules around closing are strict and unintuitive:

ActionResult
Receive from closed channelReturns zero value + false for ok
Send on closed channelpanic
Close already-closed channelpanic
Close nil channelpanic
Receive from nil channelBlocks forever
Send on nil channelBlocks forever
AI pitfall
AI sometimes closes channels from the receiver side. Only the sender should close a channel, the receiver has no way to know if the sender will try to send again. When the sender sends on a closed channel, your program panics.
AI pitfall
AI generates code that closes channels in multiple goroutines. If two goroutines both call close(ch), the second one panics. The rule: exactly one goroutine owns closing a channel, and it's always the sender (or a coordinator that knows all senders are done).
05

The deadly deadlockWhat is deadlock?A situation where two or more operations are stuck waiting on each other forever, so none of them can proceed. patterns

AI generates these constantly. Learn to spot them:

// DEADLOCK 1: unbuffered send and receive in same goroutine
func main() {
    ch := make(chan int)
    ch <- 1        // blocks forever - no other goroutine to receive
    fmt.Println(<-ch)
}

// DEADLOCK 2: two goroutines waiting on each other
func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        val := <-ch1  // waits for ch1
        ch2 <- val    // never reaches here
    }()

    val := <-ch2      // waits for ch2 - circular dependency
    ch1 <- val
}

// DEADLOCK 3: forgetting to close a channel with range
func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        // forgot close(ch)!
    }()
    for v := range ch { // blocks forever after receiving 2
        fmt.Println(v)
    }
}
AI pitfall
AI-generated pipeline code frequently forgets to close output channels. The downstream range loop blocks forever waiting for more values. Every pipeline stage that returns a channel must close it when done.
06

ChannelWhat is channel?A typed conduit in Go used to pass values between goroutines - can be unbuffered (synchronous) or buffered (async queue). patterns you'll see AI generate

Signaling with empty struct

When you only need to signal an event (no data), use chan struct{}, zero memory per signal:

done := make(chan struct{})

go func() {
    doWork()
    close(done) // signal completion to all receivers
}()

<-done // wait for signal

Closing a channel unblocks all receivers simultaneously. This is how you broadcast "done" to multiple goroutines.

Generator pattern

A function that returns a receive-only channel:

func fibonacci(n int) <-chan int {
    ch := make(chan int)
    go func() {
        defer close(ch)
        a, b := 0, 1
        for i := 0; i < n; i++ {
            ch <- a
            a, b = b, a+b
        }
    }()
    return ch
}

for v := range fibonacci(10) {
    fmt.Println(v)
}
When AI generates this pattern, verify it closes the channel. Also verify the goroutine can exit if the consumer stops reading early (it can't in this example, the goroutine leaks if you break out of the range loop early). Production code needs a context.Context for cancellation.