Thuta Learning
ProjectsProgrammingbeginner

Mini Project: Task Manager (Part 2) - Methods & Error Handling

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Apply Mini Project: Task Manager (Part 2) - Methods & Error Handling in a real, hands-on project
  • Write the code yourself and run it
  • Build an entire project step by step

Let's think about this for a second

Part 1 used a function-based approach, but in Part 2 we'll refactor the code to be more solid using the receiver method pattern you learned in the Methods chapter. We'll turn the tasks slice into a field of a TaskManager struct, and rewrite Add, List, Complete, and Remove as methods on that struct. Since Complete and Remove need to handle what happens when an ID is wrong, we'll put the error return pattern from the Errors chapter into practice. By the end of this stage, the project will follow a more proper Go design, combining struct, methods, and error handling.

Let's build it for real

Define a TaskManager struct with a single tasks []Task field. Using a pointer receiver (m *TaskManager), write four methods: Add(title string), List(), Complete(id int) error, and Remove(id int) error. In both Complete and Remove, use a for loop to find the ID, and if it's not found, return fmt.Errorf("task %d not found", id). In Remove, use the slice trick (append(tasks[:i], tasks[i+1:]...)) to drop the task from the list. In main(), call Complete/Remove, check the error with if err != nil, and print it.

Code Example

go
package main

import "fmt"

type Task struct {
	ID    int
	Title string
	Done  bool
}

type TaskManager struct {
	tasks []Task
}

func (m *TaskManager) Add(title string) {
	t := Task{ID: len(m.tasks) + 1, Title: title}
	m.tasks = append(m.tasks, t)
}

func (m *TaskManager) List() {
	for _, t := range m.tasks {
		status := "[ ]"
		if t.Done {
			status = "[x]"
		}
		fmt.Printf("%s %d. %s\n", status, t.ID, t.Title)
	}
}

func (m *TaskManager) Complete(id int) error {
	for i := range m.tasks {
		if m.tasks[i].ID == id {
			m.tasks[i].Done = true
			return nil
		}
	}
	return fmt.Errorf("task %d not found", id)
}

func (m *TaskManager) Remove(id int) error {
	for i, t := range m.tasks {
		if t.ID == id {
			m.tasks = append(m.tasks[:i], m.tasks[i+1:]...)
			return nil
		}
	}
	return fmt.Errorf("task %d not found", id)
}

func main() {
	manager := &TaskManager{}
	manager.Add("Learn Go basics")
	manager.Add("Build task manager")

	if err := manager.Complete(1); err != nil {
		fmt.Println("Error:", err)
	}
	if err := manager.Remove(5); err != nil {
		fmt.Println("Error:", err)
	}
	manager.List()
}
You should see
After completing task 1, trying to remove ID 5 will print "Error: task 5 not found".

Try it in 5 minutes

Within 5 minutes, add a new method, Update(id int, newTitle string) error, and implement it yourself so it can find a task by ID and change its title.

One quick word of caution

Keep in mind that append(m.tasks[:i], m.tasks[i+1:]...) inside the Remove method shares the slice's underlying array, so a data race is possible if there's concurrent access.

Easy traps

  • Writing the TaskManager methods with a value receiver (m TaskManager), so Add/Complete calls never actually touch the original struct - if you want to modify the slice, you need a pointer receiver (m *TaskManager)
  • Returning an error but forgetting to check it with if err != nil inside main(), so the error just gets ignored - in Go you need to handle errors right away

Now try it yourself

Within 5 minutes, add a new method, Update(id int, newTitle string) error, and implement it yourself so it can find a task by ID and change its title.

You'll know it worked when: After completing task 1, trying to remove ID 5 will print "Error: task 5 not found".

Mini Project: Task Manager (Part 2) - Methods & Error Handling | Thuta Learning