var vs := — Optimize the Code¶
Practice optimizing variable declaration patterns for clarity, correctness, and performance.
How to Use¶
- Read the original code and understand what it does
- Identify what can be improved (clarity, correctness, or performance)
- Write your optimized version
- Compare with the provided solution
Categories¶
| Category | Description |
|---|---|
| Clarity | Make code more readable and expressive |
| Memory | Reduce unnecessary allocations |
| Performance | Faster execution, less GC pressure |
| Correctness | Fix logical issues related to declaration |
Exercise 1: Remove Redundant Type Annotations (Easy / Clarity)¶
What the code does: Declares several local variables with explicit types that match the right-hand side.
The problem: The types are redundant — they can be inferred. This adds noise without adding information.
package main
import "fmt"
func main() {
var name string = "Alice"
var age int = 30
var height float64 = 1.75
var active bool = true
var score int = 0
fmt.Println(name, age, height, active, score)
}
Hint
When the type can be inferred from the right-hand side, you can use `:=` inside functions. For zero values, use `var x T` without a value.Optimized Solution
**Why it's better:** - `name := "Alice"` is more idiomatic Go inside functions - `var score int` clearly signals "this starts at zero by design" - Less visual noise — reader focuses on values, not redundant type names - Fewer characters to type and readExercise 2: C-Style Declarations at Top of Function (Easy / Clarity)¶
What the code does: Processes an HTTP request, but declares all variables at the top in C style.
The problem: Variables are declared far from where they are used. Readers must scroll to understand what each variable holds. Some may not be used in certain code paths.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Request struct{ UserID int }
type User struct{ Name string }
type Response struct{ Message string }
func handleRequest(r *http.Request) {
var req Request
var user User
var resp Response
var data []byte
var err error
data, err = readBody(r)
if err != nil {
fmt.Println("error reading body")
return
}
err = json.Unmarshal(data, &req)
if err != nil {
fmt.Println("error parsing body")
return
}
user, err = getUser(req.UserID)
if err != nil {
fmt.Println("error getting user")
return
}
resp = buildResponse(user)
fmt.Println(resp.Message)
}
func readBody(r *http.Request) ([]byte, error) { return nil, nil }
func getUser(id int) (User, error) { return User{Name: "Alice"}, nil }
func buildResponse(u User) Response { return Response{Message: "Hello " + u.Name} }
Hint
Declare each variable at the point where it is first assigned. Use `:=` for variables that get their value from function calls.Optimized Solution
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func handleRequestOptimized(r *http.Request) {
data, err := readBody(r)
if err != nil {
fmt.Println("error reading body")
return
}
var req Request
if err = json.Unmarshal(data, &req); err != nil {
fmt.Println("error parsing body")
return
}
user, err := getUser(req.UserID)
if err != nil {
fmt.Println("error getting user")
return
}
resp := buildResponse(user)
fmt.Println(resp.Message)
}
Exercise 3: Unnecessary make for Zero-Value Types (Medium / Memory)¶
What the code does: Creates several types that have useful zero values but initializes them unnecessarily.
The problem: sync.Mutex, bytes.Buffer, and sync.WaitGroup all have useful zero values. Explicitly initializing them adds noise.
package main
import (
"bytes"
"fmt"
"sync"
)
func process() {
mu := sync.Mutex{} // unnecessary
buf := bytes.Buffer{} // unnecessary
wg := sync.WaitGroup{} // unnecessary
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
buf.WriteString("hello")
mu.Unlock()
}()
wg.Wait()
fmt.Println(buf.String())
}
func main() {
process()
}
Hint
These types from the standard library are designed to work with their zero values. Use `var` to declare them without initialization.Optimized Solution
package main
import (
"bytes"
"fmt"
"sync"
)
func processOptimized() {
var mu sync.Mutex // zero value = unlocked
var buf bytes.Buffer // zero value = empty buffer
var wg sync.WaitGroup // zero value = counter at 0
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
buf.WriteString("hello")
mu.Unlock()
}()
wg.Wait()
fmt.Println(buf.String())
}
func main() {
processOptimized()
}
Exercise 4: Pre-Allocate Slice in Loop (Medium / Memory + Performance)¶
What the code does: Collects transformed items into a slice.
The problem: The slice grows through repeated append calls, causing multiple reallocations and copies.
package main
import "fmt"
func doubleAll(nums []int) []int {
var result []int // nil slice, starts with no capacity
for _, n := range nums {
result = append(result, n*2)
// Each append may trigger reallocation:
// capacity: 0 → 1 → 2 → 4 → 8 → ...
}
return result
}
func main() {
input := make([]int, 1000)
for i := range input {
input[i] = i
}
output := doubleAll(input)
fmt.Println(len(output))
}
Hint
When you know the final size of the slice, use `make([]T, 0, n)` to pre-allocate with the right capacity.Optimized Solution
package main
import "fmt"
func doubleAllOptimized(nums []int) []int {
result := make([]int, 0, len(nums)) // pre-allocated
for _, n := range nums {
result = append(result, n*2) // no reallocation needed
}
return result
}
// Even simpler: use a pre-sized slice with index assignment
func doubleAllFastest(nums []int) []int {
result := make([]int, len(nums)) // pre-sized, no append needed
for i, n := range nums {
result[i] = n * 2
}
return result
}
func main() {
input := make([]int, 1000)
for i := range input {
input[i] = i
}
fmt.Println(len(doubleAllOptimized(input))) // 1000
fmt.Println(len(doubleAllFastest(input))) // 1000
}
Exercise 5: Map Pre-Allocation (Medium / Memory)¶
What the code does: Builds an index from a slice.
The problem: Map starts empty and grows incrementally, causing multiple hash table resizes.
package main
import "fmt"
type Item struct {
Key string
Value int
}
func buildIndex(items []Item) map[string]int {
index := make(map[string]int) // no size hint
for _, item := range items {
index[item.Key] = item.Value
}
return index
}
func main() {
items := make([]Item, 1000)
for i := range items {
items[i] = Item{Key: fmt.Sprintf("key%d", i), Value: i}
}
index := buildIndex(items)
fmt.Println(len(index))
}
Hint
`make(map[K]V, hint)` allows you to specify an approximate initial capacity, reducing rehash operations.Optimized Solution
package main
import "fmt"
type Item2 struct {
Key string
Value int
}
func buildIndexOptimized(items []Item2) map[string]int {
index := make(map[string]int, len(items)) // size hint
for _, item := range items {
index[item.Key] = item.Value
}
return index
}
func main() {
items := make([]Item2, 1000)
for i := range items {
items[i] = Item2{Key: fmt.Sprintf("key%d", i), Value: i}
}
index := buildIndexOptimized(items)
fmt.Println(len(index))
}
Exercise 6: Scope Reduction with if-init (Medium / Clarity)¶
What the code does: Parses a config value and uses it locally.
The problem: Variables leak into the outer scope when they are only needed inside a conditional.
package main
import (
"fmt"
"os"
"strconv"
)
func getConfig() {
portStr := os.Getenv("PORT")
port, err := strconv.Atoi(portStr)
if err != nil {
port = 8080
}
fmt.Println("Using port:", port)
timeout, err2 := strconv.Atoi(os.Getenv("TIMEOUT"))
if err2 != nil {
timeout = 30
}
fmt.Println("Using timeout:", timeout)
// portStr, port, err, err2 are all still in scope here
// even though they're no longer needed
_, _, _, _ = portStr, port, err, err2
}
func main() {
getConfig()
}
Hint
Use `if v, err := ...; err != nil` to scope variables to just the block they are needed in.Optimized Solution
package main
import (
"fmt"
"os"
"strconv"
)
func getConfigOptimized() {
port := 8080
if p, err := strconv.Atoi(os.Getenv("PORT")); err == nil {
port = p
}
fmt.Println("Using port:", port)
timeout := 30
if t, err := strconv.Atoi(os.Getenv("TIMEOUT")); err == nil {
timeout = t
}
fmt.Println("Using timeout:", timeout)
// Only port and timeout remain in scope — clean!
}
func main() {
getConfigOptimized()
}
Exercise 7: Avoid Allocation in Hot Loop (Hard / Performance)¶
What the code does: Formats log messages in a high-frequency loop.
The problem: fmt.Sprintf allocates a new string on every call. In a hot path, this generates significant GC pressure.
package main
import (
"fmt"
"io"
"os"
)
func logMessages(w io.Writer, count int) {
for i := 0; i < count; i++ {
// BAD: allocates a new string on every iteration
msg := fmt.Sprintf("[INFO] processing item %d", i)
fmt.Fprintln(w, msg)
}
}
func main() {
logMessages(os.Stdout, 5)
}
Hint
Declare a `bytes.Buffer` outside the loop (zero value), reset it on each iteration, and write directly to it. This reuses the buffer's backing memory.Optimized Solution
package main
import (
"bytes"
"fmt"
"io"
"os"
)
func logMessagesOptimized(w io.Writer, count int) {
var buf bytes.Buffer // declared once, zero value is ready
for i := 0; i < count; i++ {
buf.Reset() // reuse backing array — no new allocation
fmt.Fprintf(&buf, "[INFO] processing item %d", i)
buf.WriteByte('\n')
w.Write(buf.Bytes())
}
}
// Even better for high-frequency: use sync.Pool
var logBufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
func logMessagesPool(w io.Writer, count int) {
for i := 0; i < count; i++ {
buf := logBufPool.Get().(*bytes.Buffer)
buf.Reset()
fmt.Fprintf(buf, "[INFO] processing item %d", i)
buf.WriteByte('\n')
w.Write(buf.Bytes())
logBufPool.Put(buf)
}
}
func main() {
logMessagesOptimized(os.Stdout, 5)
}
Exercise 8: Avoid Repeated String Building (Medium / Performance)¶
What the code does: Builds a comma-separated string from a slice.
The problem: String concatenation with += creates a new string on every iteration (strings are immutable in Go).
package main
import "fmt"
func joinStrings(items []string) string {
result := ""
for i, item := range items {
if i > 0 {
result += ", " // new string allocation every iteration!
}
result += item // another allocation
}
return result
}
func main() {
words := []string{"apple", "banana", "cherry", "date"}
fmt.Println(joinStrings(words))
}
Hint
Use `strings.Builder` (zero value is ready) or `strings.Join` for string concatenation.Optimized Solution
package main
import (
"fmt"
"strings"
)
// Option 1: strings.Builder (zero value ready)
func joinStringsBuilder(items []string) string {
var sb strings.Builder // zero value is an empty builder
for i, item := range items {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(item)
}
return sb.String()
}
// Option 2: strings.Join (simplest)
func joinStringsJoin(items []string) string {
return strings.Join(items, ", ")
}
// Option 3: strings.Builder with pre-allocation hint
func joinStringsPrealloc(items []string) string {
total := 0
for _, item := range items {
total += len(item) + 2 // +2 for ", "
}
var sb strings.Builder
sb.Grow(total) // pre-allocate
for i, item := range items {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(item)
}
return sb.String()
}
func main() {
words := []string{"apple", "banana", "cherry", "date"}
fmt.Println(joinStringsBuilder(words))
fmt.Println(joinStringsJoin(words))
fmt.Println(joinStringsPrealloc(words))
}
Exercise 9: Remove Intermediate Variable (Easy / Clarity)¶
What the code does: Computes a result through an intermediate variable that is used only once.
The problem: Intermediate variables that are used exactly once add noise without adding clarity.
package main
import "fmt"
func greet(name string) string {
greeting := "Hello, " + name + "!"
return greeting
}
func isEven(n int) bool {
remainder := n % 2
result := remainder == 0
return result
}
func clamp(value, min, max int) int {
var clamped int
if value < min {
clamped = min
} else if value > max {
clamped = max
} else {
clamped = value
}
return clamped
}
func main() {
fmt.Println(greet("Alice"))
fmt.Println(isEven(4))
fmt.Println(clamp(15, 0, 10))
}
Hint
Intermediate variables used only once can often be inlined. But `clamp` needs the var pattern — why?Optimized Solution
package main
import "fmt"
// Inline the intermediate variable
func greetOptimized(name string) string {
return "Hello, " + name + "!"
}
// Remove unnecessary intermediate
func isEvenOptimized(n int) bool {
return n%2 == 0
}
// clamp is actually a good use case for var — keep it
// But we can still simplify with an idiomatic pattern
func clampOptimized(value, min, max int) int {
if value < min {
return min
}
if value > max {
return max
}
return value
}
func main() {
fmt.Println(greetOptimized("Alice"))
fmt.Println(isEvenOptimized(4))
fmt.Println(clampOptimized(15, 0, 10))
}
Exercise 10: Replace var+assign with := in Loop (Easy / Clarity)¶
What the code does: Searches for an item in a slice.
The problem: Variables are declared with var at the top of a loop body when they could be declared inline with :=.
package main
import "fmt"
type Product struct {
ID int
Name string
Price float64
}
func findCheapest(products []Product, maxPrice float64) (Product, bool) {
var found bool
var best Product
for _, p := range products {
var eligible bool
eligible = p.Price <= maxPrice
if eligible {
var better bool
if !found {
better = true
} else {
better = p.Price < best.Price
}
if better {
best = p
found = true
}
}
}
return best, found
}
func main() {
products := []Product{
{1, "Widget", 9.99},
{2, "Gadget", 4.99},
{3, "Doohickey", 14.99},
{4, "Thingamajig", 3.99},
}
if p, ok := findCheapest(products, 10.0); ok {
fmt.Printf("Cheapest under $10: %s ($%.2f)\n", p.Name, p.Price)
}
}
Hint
`eligible` and `better` are computed booleans — they should be `:=` inline. The outer `found` and `best` genuinely need `var` because they accumulate state.Optimized Solution
package main
import "fmt"
type Product2 struct {
ID int
Name string
Price float64
}
func findCheapestOptimized(products []Product2, maxPrice float64) (Product2, bool) {
var best Product2
found := false
for _, p := range products {
if p.Price > maxPrice {
continue
}
if !found || p.Price < best.Price {
best = p
found = true
}
}
return best, found
}
func main() {
products := []Product2{
{1, "Widget", 9.99},
{2, "Gadget", 4.99},
{3, "Doohickey", 14.99},
{4, "Thingamajig", 3.99},
}
if p, ok := findCheapestOptimized(products, 10.0); ok {
fmt.Printf("Cheapest under $10: %s ($%.2f)\n", p.Name, p.Price)
}
}
Exercise 11: interface{} to any + Type-Safe Wrapper (Medium / Clarity)¶
What the code does: Stores configuration values in a map.
The problem: Using interface{} (pre-Go 1.18 style) obscures types. Modern Go (1.18+) uses any. Beyond that, the variable declarations can be improved.
package main
import "fmt"
type Config map[string]interface{}
func loadConfig() Config {
var cfg Config
cfg = make(Config) // separate declaration and initialization
var host interface{}
host = "localhost"
cfg["host"] = host
var port interface{}
port = 8080
cfg["port"] = port
return cfg
}
func getPort(cfg Config) int {
var portVal interface{}
var ok bool
portVal, ok = cfg["port"]
if !ok {
return 8080
}
var port int
port, ok = portVal.(int)
if !ok {
return 8080
}
return port
}
func main() {
cfg := loadConfig()
fmt.Println("Port:", getPort(cfg))
}
Hint
Combine declarations with their initializations using `:=`. Replace `interface{}` with `any` (Go 1.18+). Remove intermediate single-use variables.Optimized Solution
package main
import "fmt"
type Config2 map[string]any // any is an alias for interface{} (Go 1.18+)
func loadConfig2() Config2 {
cfg := Config2{ // declare and initialize in one step
"host": "localhost",
"port": 8080,
}
return cfg
}
func getPort2(cfg Config2) int {
portVal, ok := cfg["port"]
if !ok {
return 8080
}
port, ok := portVal.(int)
if !ok {
return 8080
}
return port
}
func main() {
cfg := loadConfig2()
fmt.Println("Port:", getPort2(cfg))
}
Exercise 12: sync.Pool for Repeated Allocations (Hard / Performance + Memory)¶
What the code does: Processes many JSON payloads in an HTTP handler.
The problem: Every request allocates a new bytes.Buffer for decoding. Under load, this creates significant GC pressure.
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type Payload struct {
Name string `json:"name"`
Value int `json:"value"`
}
func handlePayload(w http.ResponseWriter, r *http.Request) {
buf := new(bytes.Buffer) // NEW allocation per request
_, err := buf.ReadFrom(r.Body)
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
var payload Payload
if err := json.Unmarshal(buf.Bytes(), &payload); err != nil {
http.Error(w, "parse error", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "received: %s=%d", payload.Name, payload.Value)
}
Hint
Use `sync.Pool` with a package-level `var` to reuse `bytes.Buffer` instances across requests. Remember to `Reset()` before use and `Put()` after.Optimized Solution
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"sync"
)
type Payload2 struct {
Name string `json:"name"`
Value int `json:"value"`
}
// Package-level pool — declared once, reused across all requests
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func handlePayloadOptimized(w http.ResponseWriter, r *http.Request) {
// Get buffer from pool (may reuse existing one)
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() // clear previous content
defer bufPool.Put(buf) // return to pool when done
if _, err := buf.ReadFrom(r.Body); err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
var payload Payload2
if err := json.Unmarshal(buf.Bytes(), &payload); err != nil {
http.Error(w, "parse error", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "received: %s=%d", payload.Name, payload.Value)
}
func main() {
http.HandleFunc("/payload", handlePayloadOptimized)
// http.ListenAndServe(":8080", nil)
fmt.Println("Handler registered")
}