The Array Principle: Why Good Software Starts by Choosing the Right Constraints
Hatched by Kai Nguyen
Aug 06, 2026
10 min read
0 views
88%
What if one of the most basic structures in programming contains a lesson about nearly every difficult engineering decision?
An array seems too simple to deserve philosophical attention. It is a sequence of items stored next to one another in memory. Its elements share a type. Its size is fixed, or at least constrained by the way the structure is created. Yet those limitations are precisely what make it useful. Because the array commits to a particular shape, a computer can access its contents quickly and predictably.
That same logic applies far beyond data structures. Good software design begins when we stop asking, "Which pattern should I use?" and start asking, "What must be true about this problem?" The answer often reveals that every useful design is an arrangement of constraints. A solution becomes elegant not by escaping limitations, but by choosing the right ones.
The quality of a design depends less on how many options it preserves than on whether its constraints match reality.
This creates a deeper connection between first principles thinking and data structure design: both are methods for discovering the shape of a problem before attempting to solve it.
The temptation to choose a solution too early
Software developers often approach problems through familiar names. Perhaps the feature calls for a repository, an observer, a factory, a queue, or a particular framework. Naming a pattern feels like progress because it converts uncertainty into recognition. But recognition is not the same as understanding.
A design pattern is useful only because it corresponds to a recurring class of problems. If the class has not been identified, the pattern is little more than a costume. The developer may produce code that looks sophisticated while quietly solving the wrong problem.
Imagine being asked to build a waiting list for a clinic. A developer who begins with tools might immediately choose a queue library. A developer who begins with the domain asks different questions:
- Does the first person to arrive always get served first?
- Can urgent cases move ahead of others?
- Can a patient cancel an appointment?
- Does the system need to know where someone stands in line?
- Must the list survive a server restart?
- How many entries are expected, and how often will the list change?
The answers determine the structure. A simple queue may fit an ordinary first in, first out process. A priority queue may fit urgent care. A database table may be necessary if persistence matters. A more complex arrangement may be justified if arbitrary removal and ranking are central operations.
The crucial act is not selecting among these options. It is identifying the operations and guarantees the domain requires. Only then does the choice become nearly obvious.
This is what first principles thinking contributes. It strips away inherited assumptions until the problem can be described in terms of fundamental truths: what users need, what the domain permits, what the system must guarantee, and what the chosen tools can actually do.
An array is a lesson in honest commitment
Consider the array at its most basic level. It stores values of the same type in consecutive memory locations. If the computer knows the starting address and the position of an item, it can calculate where that item lives. The access rule is simple:
location of item = starting location + position multiplied by item size
This is why retrieving an element by index is fast. The structure has made several commitments in advance. The values have a uniform type. Their positions are meaningful. Their storage is contiguous. The size and layout are known well enough to support direct calculation.
Those commitments are not incidental details. They are the mechanism of the benefit.
An array is fast because it refuses to be many things. It is not an ideal structure for arbitrary insertion in the middle, because moving later elements may be necessary. It is not naturally suited to values of unrelated shapes. It may waste space if its allocated capacity is much larger than its actual contents. Its strengths and weaknesses flow from the same underlying decision.
This gives us a powerful engineering principle:
Every capability has a structural cost, and every limitation can be the source of a capability.
A system that supports instant lookup by identifier may require extra memory for an index. A system that permits flexible schemas may sacrifice validation and predictability. A system that handles every possible workflow may become difficult to understand and test. There is no design with only benefits. There are only designs whose costs are appropriate for their purpose.
First principles thinking makes those costs visible. Instead of asking whether an array is good or bad, we ask: what operations matter, what constraints are acceptable, and what performance or correctness guarantees are necessary?
From fundamental truths to useful structure
The most productive way to reason about a design is to separate the problem into three layers.
1. Reality
What is true independently of the implementation?
A customer can place an order. An order can be paid or unpaid. A payment may fail. A document may have many revisions. A user may need an answer within a particular time. These are facts about the domain or the user experience, not about programming languages.
2. Operations
What actions must the system perform, and what actions occur most often?
A feature may need to find records by email, append events, remove expired sessions, sort results, or retrieve the most recent activity. Frequency matters. An operation performed once a day should not necessarily dictate the same structure as one performed millions of times per second.
3. Representation
What arrangement of data and behavior makes those operations reliable and efficient?
Only at this stage should we decide between an array, a linked structure, a hash based lookup, a tree, a database index, or a more specialized design. Representation is an answer to the first two layers. It is not a substitute for them.
This separation prevents a common category error: treating the current representation as if it were the underlying problem.
Suppose a team stores a list of products in an array and later discovers that searching by product code is slow. The superficial response may be to optimize the loop. The deeper response is to ask whether positional order was ever the important truth. If the core operation is lookup by code, then the array may be the wrong representation. A map or an additional index might better reflect the domain.
The same reasoning works at architectural scale. If a service is difficult to extend, the answer may not be a more elaborate abstraction. Perhaps the domain has been divided along technical layers rather than meaningful responsibilities. If users are confused by a workflow, adding more configuration may not help. Perhaps the product has failed to identify the one decision the user is trying to make.
In each case, the remedy begins with decomposition: reduce the problem to its essential components, identify the important operations, then reassemble a structure around them.
The hidden role of priority
There is another connection between arrays and first principles that is easy to miss: both force prioritization.
An array cannot optimize every operation simultaneously. It is excellent at indexed access, reasonably simple to iterate, and often poor at inserting elements near the front. Its design embodies a priority. It says, in effect, "Fast access by position matters more than effortless insertion anywhere."
Every software project makes a similar choice, whether the team acknowledges it or not. A team may prioritize speed of delivery, learning, reliability, flexibility, cost, or user delight. Trouble begins when these priorities remain implicit. Developers then attempt to build a system that is maximally fast, infinitely flexible, perfectly abstract, cheap, and easy to change, all at once.
That is not ambition. It is the refusal to choose.
When priorities conflict, first principles thinking asks which goal is fundamental for the current situation. If the immediate objective is to validate a user need, the simplest working feature may matter more than a generalized platform. If the objective is to improve technical skill, a familiar shortcut may be less valuable than implementing a small component from scratch. If the objective is reliability, a narrow design with clear invariants may be preferable to an expansive one with uncertain behavior.
The word "priority" is singular for a reason. A list of ten priorities is usually a list of wishes. Design becomes coherent when one or two governing goals are clear enough to determine what will not be optimized.
For an array, that might be constant time access by position. For a product team, it might be learning whether users will return. For a data pipeline, it might be preserving event order. Once the priority is explicit, many secondary decisions become easier because the design has a center of gravity.
A practical method for designing from the inside out
Before writing code, use the following sequence. It is deliberately slower at the beginning and faster over the life of the project.
Define the invariant
An invariant is a condition that must remain true. Examples include: every order has one owner, a queue serves the highest priority item first, an array contains only values of one type, or a published report cannot be altered without creating a new revision.
Invariants are more useful than vague goals because they can guide implementation and testing. They tell you what the system must protect even when individual functions change.
List the essential operations
Write down what users and other systems actually need to do. Include the expected frequency of each action. A structure that is ideal for reading may be poor for writing. A design that works at one thousand records may fail at one hundred million.
Do not merely list features. Translate them into operations such as append, find, update, remove, order, group, validate, and retrieve by key.
Identify the unacceptable failure
What would be most damaging: a slow response, lost data, an incorrect result, excessive cost, or a confusing workflow? This question exposes the real priority. It also prevents teams from optimizing a metric that nobody values.
Choose commitments deliberately
Select a representation whose constraints support the invariant and the important operations. If you choose an array, accept that insertion may be expensive. If you choose a flexible structure, accept that validation may require additional work. If you choose an abstraction, be able to explain which recurring problem it represents.
Test the model with a small example
Build the smallest concrete case that could disprove your assumptions. A tiny prototype often reveals that the supposed central problem is not central at all. It may also show that a simple structure is enough, which is a valuable discovery rather than a lack of sophistication.
This process is not an argument for avoiding patterns, frameworks, or powerful tools. It is an argument for using them as compressed knowledge rather than as rituals. A tool should enter the design after the problem has been made legible.
Key Takeaways
- Start with truths, not technologies. Describe the user, domain, guarantees, and constraints before choosing a pattern or tool.
- Treat data structures as bundles of tradeoffs. Ask which operations a structure makes cheap and which it makes expensive.
- Write down the invariant. A clear condition that must remain true is often more valuable than a broad feature description.
- Make priority explicit. Decide whether the current goal is speed, learning, reliability, flexibility, or something else. Do not pretend every goal is equal.
- Reassemble only after decomposing. Break the problem into reality, operations, and representation, then build the design from those parts.
The beginner often sees an array as a fact to memorize: a fixed collection of same type values stored contiguously. The experienced engineer sees a negotiation. The array trades flexibility for locality, generality for predictability, and effortless insertion for direct access.
That negotiation is happening in every system, including the ones that appear abstract and unconstrained. APIs choose what clients may depend on. Databases choose which queries deserve indexes. Teams choose which work to do now and which to postpone. Products choose which user behavior to make easy and which to leave unsupported.
The central skill is therefore not knowing more structures. It is learning to recognize the commitments hidden inside them.
Good design does not eliminate constraints. It turns the right constraints into leverage.
Once you see software this way, an array stops being merely an introductory topic. It becomes a compact model of engineering itself. Understand what must be true. Decide what matters most. Accept the costs of your commitments. Then build a structure whose limitations make the desired behavior possible.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣