Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
50: Working with Buffers: bytes.Buffer
Why should I use a buffer instead of just adding strings together?
If you're just joining two or three strings, honestly, just use the + operator or fmt.Sprintf. It's readable and fast enough. But here is where you'll run into trouble: when you're building a large string inside a loop.
In Go, strings are immutable. Every time you do s += "more text", Go isn't just appending to the end of the existing string; it's creating a brand new string in memory and copying the old contents over. If you do this a thousand times in a loop, you're creating a thousand temporary strings that the garbage collector eventually has to clean up. It's a performance killer.
bytes.Buffer solves this by maintaining a slice of bytes that grows dynamically. It allocates more space in chunks, meaning it doesn't have to re-allocate and copy every single time you add a piece of data. I usually reach for a buffer the moment I see a loop that's assembling a long message or a document.
How do I actually put data in and get it out?
Using a buffer is pretty intuitive. You create an instance, write to it, and then call .String() when you're done. I personally use WriteString most of the time because it's cleaner than converting everything to []byte.
package main
import (
"bytes"
"fmt"
)
func main() {
var buf bytes.Buffer
// We can write strings directly
buf.WriteString("User Report\n")
buf.WriteString("------------\n")
// Or we can write raw bytes
buf.Write([]byte{65, 66, 67}) // Writes "ABC"
// You can even use fmt.Fprintf to format data directly into the buffer
fmt.Fprintf(&buf, "\nID: %d | Status: %s", 101, "Active")
fmt.Println(buf.String())
}
One thing to keep in mind: buf.String() returns a copy of the data as a string. If you're passing this data straight into another function that accepts a byte slice, use buf.Bytes() instead to avoid that extra allocation.
Can I use this with other Go interfaces like io.Writer?
This is actually the "secret sauce" of bytes.Buffer. Because it implements the io.Writer and io.Reader interfaces, it can act as a bridge between different parts of your program.
Imagine you have a function that encodes data (like JSON or XML) and requires an io.Writer. Instead of writing that data directly to a file or a network socket—where you can't "take it back" if something goes wrong—you can write it to a buffer first. Once you've verified the buffer contains what you expect, you can then send that buffer's contents to the final destination.
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
user := User{Name: "Alice", Age: 30}
var buf bytes.Buffer
// json.NewEncoder takes an io.Writer.
// bytes.Buffer fits that perfectly.
encoder := json.NewEncoder(&buf)
if err := encoder.Encode(user); err != nil {
fmt.Println("Error encoding:", err)
return
}
fmt.Println("Buffered JSON:", buf.String())
}
I find this incredibly useful for unit testing. You can pass a bytes.Buffer into a function that normally writes to a file, then simply inspect the buffer's string output to assert that the function wrote the correct data.
📋 Practical Task
Exercise: Building a Dynamic CSV Row Generator
In this exercise, you will create a function that takes a slice of strings and converts them into a single CSV-formatted row. Instead of using string concatenation, you must use bytes.Buffer to build the resulting string.
- Create a function
GenerateCSVRow(columns []string) string. - The function should join each element of the slice with a comma (
,). - Ensure there is no trailing comma at the end of the string.
- Use
bytes.Bufferand itsWriteStringmethod to assemble the row. - Test your function with a slice like
[]string{"John Doe", "Software Engineer", "New York", "USA"}to ensure the output is"John Doe,Software Engineer,New York,USA".
There are no comments for now.