11 - Putting It All Together

📋 Jump to Takeaways

You've learned variables, decisions, loops, functions, slices, maps, structs, errors, and packages. This final lesson brings everything together into a real application: a Contact Book.

No new concepts. Just applying everything you already know.

The Project

A CLI contact book that lets you:

  • Add contacts (name, phone, email)
  • List all contacts
  • Search by name
  • Save contacts to a file
  • Load contacts from a file on startup

Project Structure

contacts/
  go.mod
  main.go
  contact/
    contact.go
    storage.go

Two packages: contact handles the data and file I/O, main handles the menu and user interaction.

Create the folders and initialize the module:

mkdir contacts
cd contacts
mkdir contact
go mod init contacts

contact/contact.go

The Contact struct and all operations on the contact list:

package contact

import (
	"fmt"
	"strings"
)

type Contact struct {
	Name  string
	Phone string
	Email string
}

func (c Contact) Display() string {
	return fmt.Sprintf("  %s | %s | %s", c.Name, c.Phone, c.Email)
}

func Add(list []Contact, name, phone, email string) ([]Contact, error) {
	if name == "" {
		return list, fmt.Errorf("name cannot be empty")
	}
	c := Contact{Name: name, Phone: phone, Email: email}
	return append(list, c), nil
}

func Search(list []Contact, query string) []Contact {
	var results []Contact
	q := strings.ToLower(query)
	for _, c := range list {
		if strings.Contains(strings.ToLower(c.Name), q) {
			results = append(results, c)
		}
	}
	return results
}

This uses:

  • Structs: Contact groups name, phone, and email
  • Methods: Display formats a contact for printing
  • Slices: the contact list is []Contact
  • Functions: Add and Search operate on the list
  • Errors: Add returns an error if the name is empty
  • Strings: ToLower and Contains for case-insensitive search

contact/storage.go

Save and load contacts to a text file:

package contact

import (
	"fmt"
	"os"
	"strings"
)

const separator = "|"

func Save(list []Contact, filename string) error {
	var lines []string
	for _, c := range list {
		line := c.Name + separator + c.Phone + separator + c.Email
		lines = append(lines, line)
	}
	data := strings.Join(lines, "\n")
	err := os.WriteFile(filename, []byte(data), 0644)
	if err != nil {
		return fmt.Errorf("could not save: %s", err)
	}
	return nil
}

func Load(filename string) ([]Contact, error) {
	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	var list []Contact
	lines := strings.Split(string(data), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		parts := strings.SplitN(line, separator, 3)
		if len(parts) != 3 {
			continue
		}
		c := Contact{
			Name:  strings.TrimSpace(parts[0]),
			Phone: strings.TrimSpace(parts[1]),
			Email: strings.TrimSpace(parts[2]),
		}
		list = append(list, c)
	}
	return list, nil
}

This uses:

  • File I/O: os.WriteFile and os.ReadFile
  • Error handling: every operation that can fail returns an error
  • Strings: Join, Split, TrimSpace for parsing
  • Loops: iterate lines to build the contact list
  • Slices: build up the list with append

main.go

The menu loop and user interaction:

package main

import (
	"bufio"
	"contacts/contact"
	"fmt"
	"os"
	"strings"
)

const filename = "contacts.txt"

var scanner = bufio.NewScanner(os.Stdin)

func readLine(prompt string) string {
	fmt.Print(prompt)
	scanner.Scan()
	return strings.TrimSpace(scanner.Text())
}

func main() {
	list, err := contact.Load(filename)
	if err != nil {
		list = []contact.Contact{}
	} else {
		fmt.Printf("Loaded %d contacts.\n", len(list))
	}

	for {
		fmt.Println()
		fmt.Println("=== Contact Book ===")
		fmt.Println("1. Add contact")
		fmt.Println("2. List all")
		fmt.Println("3. Search")
		fmt.Println("4. Save")
		fmt.Println("5. Quit")

		choice := readLine("> ")

		switch choice {
		case "1":
			name := readLine("Name: ")
			phone := readLine("Phone: ")
			email := readLine("Email: ")

			var addErr error
			list, addErr = contact.Add(list, name, phone, email)
			if addErr != nil {
				fmt.Println("Error:", addErr)
			} else {
				fmt.Println("Contact added.")
			}

		case "2":
			if len(list) == 0 {
				fmt.Println("No contacts yet.")
			} else {
				fmt.Printf("Contacts (%d):\n", len(list))
				for _, c := range list {
					fmt.Println(c.Display())
				}
			}

		case "3":
			query := readLine("Search: ")
			results := contact.Search(list, query)
			if len(results) == 0 {
				fmt.Println("No matches found.")
			} else {
				fmt.Printf("Found %d match(es):\n", len(results))
				for _, c := range results {
					fmt.Println(c.Display())
				}
			}

		case "4":
			saveErr := contact.Save(list, filename)
			if saveErr != nil {
				fmt.Println("Error:", saveErr)
			} else {
				fmt.Printf("Saved %d contacts to %s\n", len(list), filename)
			}

		case "5":
			saveErr := contact.Save(list, filename)
			if saveErr != nil {
				fmt.Println("Warning: could not auto-save:", saveErr)
			}
			fmt.Println("Goodbye!")
			return

		default:
			fmt.Println("Invalid choice.")
		}
	}
}

Running It

From the contacts folder, run:

go run .

go run . compiles and runs all .go files in the current directory. Here's what a session looks like:

Loaded 0 contacts.

=== Contact Book ===
1. Add contact
2. List all
3. Search
4. Save
5. Quit
> 1
Name: Alice
Phone: 555-1234
Email: [email protected]
Contact added.

=== Contact Book ===
> 1
Name: Bob
Phone: 555-5678
Email: [email protected]
Contact added.

=== Contact Book ===
> 2
Contacts (2):
  Alice | 555-1234 | [email protected]
  Bob | 555-5678 | [email protected]

=== Contact Book ===
> 3
Search: ali
Found 1 match(es):
  Alice | 555-1234 | [email protected]

=== Contact Book ===
> 5
Goodbye!

Run it again and your contacts are still there, loaded from contacts.txt.

What You Built

Every concept from this course is in this application:

Concept Where it's used
Variables Name, phone, email, menu choice, filename
Decisions Menu switch, empty list checks, error checks
Loops Menu loop, iterating contacts, search loop
Functions Add, Search, Save, Load, readLine
Slices Contact list grows with append, search returns a filtered slice
Structs Contact type with fields and a Display method
Errors File I/O errors, validation errors, all checked
Packages contact/ package with contact.go and storage.go

Ideas to Extend

Now that you have a working app, try adding:

  • Delete a contact: search by name, remove from the slice
  • Edit a contact: find by name, update fields
  • Sort contacts: alphabetically by name using sort.Slice
  • Count contacts: show total in the menu header
  • Export to CSV: change the save format to comma-separated values

Each extension uses concepts you already know. No new lessons needed.

What to Learn Next

You now know how to write programs. You understand variables, control flow, data structures, functions, error handling, and code organization. That's a real foundation.

Go Essentials (the next course on ByteLearn) picks up where this one leaves off. It covers:

  • Pointers and memory (why & and * exist)
  • Interfaces (making different types work the same way)
  • Concurrency (doing multiple things at once with goroutines)
  • More on error handling (wrapping, custom error types)
  • A capstone CLI project

Other directions:

  • The Go Tour (go.dev/tour) is the official interactive tutorial. Now that you know the basics, it'll make much more sense.
  • Go by Example (gobyexample.com) is a quick reference for every Go feature with runnable code.
  • Build something. A CLI tool, a web scraper, a simple API. The best way to learn is to build something you actually want.

Key Takeaways

  • Everything you learned works together: variables store state, decisions branch logic, loops repeat actions, functions organize code, collections hold data, structs model entities, errors handle failure, packages structure projects
  • go run . compiles and runs all files in the current directory
  • Real programs combine all these concepts. No single feature works alone
  • Organize code by responsibility: data logic in one package, user interaction in another
  • The best way to solidify what you've learned is to build something new on your own
  • Go Essentials is the natural next step for deeper Go knowledge

📝 Ready to test your knowledge?

Answer the quiz below to mark this lesson complete.

© 2026 ByteLearn.dev. Free courses for developers. · Privacy