Update & Delete
TaskStore can already create tasks, fetch one by id, and list them all in
a stable order. Two operations are still missing, and they're the ones every
CRUD service needs to finish the set: changing a task and removing it.
Update and Delete share a shape you've already half-written — Get
already handles "does this id exist?" with a comma-ok map lookup — but here
that check has to gate a write, not just a read.
Existence first, action second
Both methods open the same way: lock s.mu, then check whether id is in
s.items before doing anything else.
if _, ok := s.items[id]; !ok {
return Task{}, false // or just false, for Delete
}
This isn't defensive padding — it's the whole point of the method. A caller
handing you an id has no way to know in advance whether that task still
exists (maybe another goroutine deleted it a moment ago), so the store has
to say so explicitly. Skip the check and Update would happily invent a
task under an id nobody created; skip it in Delete and you can't tell a
successful removal from a no-op.
Why bool, not silence
Both methods report success as a second (or only) return value instead of,
say, silently doing nothing on a miss. That's not just tidiness — it's
setting up the next chapter. Part 2 puts real HTTP handlers on top of this
store, and a handler's whole job on PUT /tasks/{id} or DELETE /tasks/{id}
is turning "task not found" into a 404 response. If Update and Delete
swallowed the miss internally, the handler would have no signal to act on.
The bool is the interface between store and handler — get it wrong here
and every caller downstream has to work around it.
Update: replace, don't merge
Update takes a full replacement — title, done, and priority — and
writes a brand new Task back into the map, keeping only the existing id:
t := Task{ID: id, Title: title, Done: done, Priority: priority}
s.items[id] = t
Notice what's not here: no reading the old task first, no field-by-field
merging. This is a "put the whole resource back" update, matching the
semantics of HTTP PUT — the caller sends the complete new state, and
whatever they don't send is gone. A PATCH-style partial update, where a
caller can change just done and leave title alone, needs a different
signature (probably pointer or optional fields) — that's Part 2's problem,
once there's a JSON request body to decide what "unset" means.
Delete: two structures, one operation
This is where the split between items and order — introduced last
lesson purely to fix iteration order — has a second job: staying in sync.
delete(s.items, id) removes the entry from the map, but order is a
separate slice, and nothing about deleting a map key touches it. Forget the
second step and the id keeps showing up in List output forever, pointing
at a task that no longer exists — a comma-ok lookup in List would paper
over the crash, but the deleted task would still leak into every listing.
The fix is a linear scan to find and splice out the id:
for i, oid := range s.order {
if oid == id {
s.order = append(s.order[:i], s.order[i+1:]...)
break
}
}
s.order[:i] is everything before the match, s.order[i+1:] everything
after; appending the second onto the first closes the gap in place. The
break matters too — ids are unique by construction (each comes from
nextID), so there's never a second match to find, and continuing to scan
would just be wasted work.
With Update and Delete in place, TaskStore supports every operation
this book's service will expose: create, get, list, update, delete. Nothing
about its method signatures needs to change even when Part 2 swaps in real
HTTP handlers and JSON encoding — those methods, and their bools, are
exactly what a handler will call.
Practice
1. Update & Delete
IntermediateRound out the store with the last two operations. Both must report whether the task existed, so handlers can turn a miss into a
404.Update(id, title string, done bool, priority int) (Task, bool)— if no task has that id, returnTask{}, false. Otherwise replace its fields (keep the same id), store it, and return the new task withtrue.Delete(id string) bool— remove the task and returntrue, orfalseif it wasn't there. Remember to remove the id froms.ordertoo, or a deleted task lingers inList.
Same locking as always.
package main import ( "strconv" "sync" ) // Task is the resource this service manages. type Task struct { ID string `json:"id"` Title string `json:"title"` Done bool `json:"done"` Priority int `json:"priority"` } // TaskStore is an in-memory, concurrency-safe store. A real service would // swap a database in behind the same methods. type TaskStore struct { mu sync.Mutex items map[string]Task order []string nextID int } // NewTaskStore returns a ready-to-use store. func NewTaskStore() *TaskStore { return &TaskStore{items: make(map[string]Task), nextID: 1} } // Create adds a new task with the next sequential ID and returns it. func (s *TaskStore) Create(title string, priority int) Task { s.mu.Lock() defer s.mu.Unlock() id := strconv.Itoa(s.nextID) s.nextID++ t := Task{ID: id, Title: title, Done: false, Priority: priority} s.items[id] = t s.order = append(s.order, id) return t } // Get returns the task with id, or ok=false if there is none. func (s *TaskStore) Get(id string) (Task, bool) { s.mu.Lock() defer s.mu.Unlock() t, ok := s.items[id] return t, ok } // List returns all tasks in the order they were created. func (s *TaskStore) List() []Task { s.mu.Lock() defer s.mu.Unlock() out := make([]Task, 0, len(s.order)) for _, id := range s.order { out = append(out, s.items[id]) } return out } // Update replaces the task's fields (keeping its id) and returns it. // ok is false if there is no task with that id. func (s *TaskStore) Update(id, title string, done bool, priority int) (Task, bool) { // TODO: lock s.mu (defer the unlock); if there is no task with this id, // return Task{}, false; otherwise build the replacement Task keeping the // same id, store it in s.items, and return it with true. return Task{}, false } // Delete removes the task with id, returning whether it existed. func (s *TaskStore) Delete(id string) bool { // TODO: lock s.mu (defer the unlock); if there is no task with this id, // return false; otherwise delete it from s.items, splice its id out of // s.order, and return true. return false } func main() {}Sign in to write and run your solution in the browser.
Sign in for hints and the solution.