Choosing the Right Shape for Data in Go

Choosing the Right Shape for Data in Go

Many small programs begin with a handful of values. A name, a count, and a status might be enough to describe a single item. As soon as a program needs to handle twenty items, however, the question changes. Instead of asking where one value belongs, the developer must decide how related values should be grouped, found, and updated. Go offers several fundamental data structures for this work. Understanding the differences between them is a useful step toward writing programs whose behavior remains understandable as their data grows.

Consider a reading catalog containing book titles, categories, and reading states. Storing each title in a separate variable would require repeated statements whenever the catalog changes. A slice offers a more organized approach: it represents a sequence of values that can be visited by position. A loop can inspect every title, count entries in a category, or gather titles that match a condition. The length of a slice tells us how many elements it currently contains. Its capacity describes how much space is available in its underlying storage before further growth may require a different allocation.

Slices also introduce an important detail about sharing data. A slice describes a region of an underlying array, so two slices can refer to overlapping storage. Updating an element through one slice may therefore be visible through another. Appending an element can retain that shared storage or allocate new storage, depending on capacity. These behaviors are worth exploring through small experiments. Rather than assuming that every assignment creates a separate copy of all elements, learners can trace which values are shared and which changes become visible elsewhere.

A map answers a different question: how can a value be found by a meaningful key rather than a numeric position? In the catalog, a unique book identifier can point to a record. The program can request an entry using that identifier without visiting each element in sequence. Maps also support adding and deleting entries. When retrieving a value, a second result can indicate whether the key exists. That distinction matters because an absent key can otherwise look similar to a stored value equal to the type's zero value. The order in which map entries are visited should not be treated as fixed.

Structs provide a way to describe what one record contains. A Book struct, for example, might hold a title, a category, and a Boolean value indicating whether reading is complete. Field names give those details clear meaning. A slice of Book values represents a series of records, while a map can associate identifiers with those records. These structures are not competing solutions to every task. They can be combined so that each one expresses a different relationship: order, lookup, or the fields belonging to one item.

Functions help maintain those relationships. One function might accept a slice and return the number of completed books. Another might receive a map and look up a record by identifier. A third could take a record and create a short description. Dividing operations this way gives each function an understandable purpose. It also encourages attention to what is passed by value and what may still refer to shared underlying data. In Go, a struct value is copied when passed as an ordinary value, but slices and maps carry references to underlying storage.

Choosing a data structure begins with asking how information will be used. Does the order of entries matter? Will records be retrieved by position or by identifier? Do several values describe one entity? Will two parts of the program need to observe updates to the same data? There is no need to answer every question through an elaborate design. For a small exercise, a slice of structs may provide all the organization required. As the task evolves, a separate lookup map might become useful, provided the program keeps both representations consistent.

Practice can follow the same progression. Begin with three book records in a slice. Write a loop that counts completed entries. Add a function that returns records from a chosen category. Next, build a map that connects identifiers to records and examine the behavior of a missing identifier. Change a field in a copied struct, then compare it with a value updated through a pointer. Record what happened after each change. These exercises illustrate how the choice of structure influences what the program can observe.

Clear data organization is not a matter of adding extra layers for their own sake. It means representing information so that a reader can answer ordinary questions about it: What does this value describe? How is it found? Who may change it? Which function is responsible for processing it? Slices, maps, and structs offer complementary answers. When their roles are understood, the path from stored information to meaningful behavior becomes easier to trace, review, and discuss.

Back to blog