Frontend Labs
⋯

Day 4 — Slices & Maps

Learning Objectives

  • Understand slice internals to avoid silent bugs and memory leaks
  • Use maps correctly in concurrent code (maps are not thread-safe)
  • Build an in-memory request log — the foundation of your WAF audit trail

Key Concepts

// Slice internals: header = (pointer, length, capacity)
s := make([]int, 0, 10) // len=0, cap=10 — no allocation on append until cap exceeded

// append may reallocate — never assume the underlying array is the same
a := []int{1, 2, 3}
b := append(a, 4)  // b may share memory with a if cap allows, or not

// Slice of slice — shares memory
original := []byte("hello world")
sub := original[6:11]  // "world" — same backing array, not a copy
sub[0] = 'W'           // modifies original too!

// Map operations
m := make(map[string]int)
m["requests"] = 42
count, ok := m["requests"]  // ok=true when key exists
if !ok { /* key not present */ }
delete(m, "requests")

// Iterating — order is random (by design, to prevent relying on it)
for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

Exercise: Ring Buffer Request Log

package main

import (
    "fmt"
    "net"
    "time"
)

type Request struct {
    IP      net.IP
    Path    string
    Status  int
    Latency time.Duration
    At      time.Time
}

// RingBuffer holds the last N requests without unbounded growth.
// Critical for WAF and rate-limiting — you don't want to buffer all requests forever.
type RingBuffer struct {
    entries []Request
    head    int
    count   int
    size    int
}

func NewRingBuffer(size int) *RingBuffer {
    return &RingBuffer{entries: make([]Request, size), size: size}
}

func (rb *RingBuffer) Add(r Request) {
    rb.entries[rb.head] = r
    rb.head = (rb.head + 1) % rb.size
    if rb.count < rb.size {
        rb.count++
    }
}

func (rb *RingBuffer) All() []Request {
    result := make([]Request, rb.count)
    for i := 0; i < rb.count; i++ {
        idx := (rb.head - rb.count + i + rb.size) % rb.size
        result[i] = rb.entries[idx]
    }
    return result
}

// IPIndex provides O(1) lookup of requests by source IP
type RequestLog struct {
    ring    *RingBuffer
    byIP    map[string][]int // IP -> slice of ring indices (approximate)
}

func NewRequestLog(size int) *RequestLog {
    return &RequestLog{
        ring: NewRingBuffer(size),
        byIP: make(map[string][]int),
    }
}

func (rl *RequestLog) Add(r Request) {
    rl.ring.Add(r)
}

func main() {
    log := NewRequestLog(1000)

    // Simulate requests
    for i := 0; i < 5; i++ {
        log.Add(Request{
            IP:      net.ParseIP("192.168.1.1"),
            Path:    fmt.Sprintf("/api/resource/%d", i),
            Status:  200,
            Latency: time.Duration(i+1) * time.Millisecond,
            At:      time.Now(),
        })
    }

    for _, r := range log.ring.All() {
        fmt.Printf("[%s] %s %s %d %v\n", r.At.Format(time.RFC3339), r.IP, r.Path, r.Status, r.Latency)
    }
}

Real-World Context

WAFs, IDS systems, and rate limiters all use ring buffers. An unbounded slice would OOM a production server. The map index enables O(1) "show all requests from IP X" — critical for incident response.


Generated from week1.md · 8 Oct 2026 16:36