Buffered vs Unbuffered Chan
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 61 of 107.
The channels we've used so far are unbuffered—they have no capacity to hold values. A send operation blocks until another goroutine receives, and vice versa. Buffered channels add internal storage, allowing sends to complete without an immediate receiver.
Create a buffered channel by passing a capacity to make:
// Unbuffered - capacity 0
unbuffered := make(chan int)
// Buffered - capacity 3
buffered := make(chan int, 3)With a buffered channel, sends only block when the buffer is full, and receives only block when the buffer is empty:
ch := make(chan string, 2)
ch <- "first" // doesn't block - buffer has space
ch <- "second" // doesn't block - buffer has space
// ch <- "third" would block - buffer is full
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Use len(ch) to check how many items are currently in the buffer, and cap(ch) for the total capacity.
When to use each: Unbuffered channels provide tight synchronization—the sender knows the receiver got the value. Buffered channels decouple sender and receiver timing, useful when producers and consumers work at different speeds. However, buffered channels can mask synchronization bugs, so prefer unbuffered unless you have a specific reason for buffering.
Challenge
EasyLet's build a batch processor that demonstrates the difference between buffered and unbuffered channels. You'll create a system where a producer sends items to a processor, using buffered channels to decouple their timing and allow the producer to work ahead.
You'll organize your code across two files:
processor.go: Define your batch processing logic using channels.Create an
Itemstruct withID(int) andValue(string) fields.Implement two functions:
Producer(items []Item, out chan Item)- Sends each item to the output channel. After sending each item, print:Produced item [ID]. After all items are sent, print the current number of items waiting in the buffer usinglen(out)in the format:Buffer has [count] items. Then close the channel.Consumer(in chan Item) []string- Receives items from the input channel and collects them into a slice of formatted strings. For each received item, the string should be:Consumed: [ID] - [Value]. Return the slice when the channel is closed.
main.go: Set up the buffered channel and coordinate the producer and consumer.Read the buffer capacity, then the number of items, followed by each item's ID and value. Create a buffered channel with the specified capacity. Run the Producer first (not as a goroutine) to fill the buffer, then run the Consumer to process all items. Print each consumed result on a separate line.
The following inputs will be provided:
- Line 1: Buffer capacity (integer)
- Line 2: Number of items (integer)
- Following lines: For each item, two lines - the item ID (integer), then its value (string)
For example, given:
3
3
1
apple
2
banana
3
cherryYour output should be:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherryNotice how with a buffer capacity of 3, the producer can send all 3 items without blocking, and the buffer shows 3 items waiting before the consumer starts. If the buffer were smaller than the number of items, the producer would block waiting for space.
Cheat sheet
Channels in Go can be unbuffered (capacity 0) or buffered (with internal storage).
Create channels with make:
// Unbuffered - capacity 0
unbuffered := make(chan int)
// Buffered - capacity 3
buffered := make(chan int, 3)Buffered channel behavior:
- Sends block only when the buffer is full
- Receives block only when the buffer is empty
ch := make(chan string, 2)
ch <- "first" // doesn't block - buffer has space
ch <- "second" // doesn't block - buffer has space
// ch <- "third" would block - buffer is full
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Check buffer status:
len(ch)- number of items currently in the buffercap(ch)- total buffer capacity
When to use:
- Unbuffered: Tight synchronization—sender knows receiver got the value
- Buffered: Decouple sender and receiver timing when they work at different speeds
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Read buffer capacity
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// Read number of items
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// Read each item's ID and value
items := make([]Item, numItems)
for i := 0; i < numItems; i++ {
line, _ = reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
value := strings.TrimSpace(line)
items[i] = Item{ID: id, Value: value}
}
// TODO: Create a buffered channel with the specified capacity
// TODO: Run the Producer (not as a goroutine) to fill the buffer
// TODO: Run the Consumer to process all items
// TODO: Print each consumed result on a separate line
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool