28 Common Patterns
By now the individual language features should feel familiar. The next step is to notice that many programs are built from the same small set of shapes.
A pattern is not a trick and not a rigid formula. It is a reusable structure of solution. Once you recognize a pattern, a new problem becomes less intimidating because part of its design is already known.
28.1 The Accumulator Loop
This is the most common loop pattern in the book, and probably the most useful.
You keep a variable that summarizes the part of the input seen so far:
nex> function sum(items: Array[Integer]): Integer
do
result := 0
across items as item do
result := result + item
end
end
Examples:
- summing numbers
- counting matches
- computing a maximum
- building a frequency table
The question to ask is: what single variable can summarize the processed prefix? Once you have that variable, the body of the loop is often nearly obvious.
28.2 Search Through a Sequence
Another recurring pattern is scanning until a target is found:
nex> function contains(items: Array[String], target: String): Boolean
do
result := false
from
let i := 0
until
i = items.length or result
do
if items.get(i) = target then
result := true
end
i := i + 1
end
end
This pattern appears in:
- membership tests
- locating the first matching element
- checking whether any item satisfies a property
The key design choice is the stopping condition: stop when the answer is known, not one iteration later.
28.3 Recursive Structure Matches Recursive Data
When data is nested like a tree, recursion often gives the cleanest code because the shape of the routine mirrors the shape of the data.
nex> function count_nodes(node: Map[String, Any]): Integer
do
result := 1
across node.get("children") as child do
result := result + count_nodes(child)
end
end
The pattern is:
- solve the problem for the current node
- solve the same problem for each child
- combine the results
Whenever the data is self-similar, ask whether the algorithm should be self-similar too.
28.4 Build a Class Around an Invariant
A good class often begins with one sentence:
“For every valid object of this class, the following must always hold…”
Examples:
- account balance is never negative
- counter is between zero and its limit
- task title is never empty
Once that invariant is known, the rest of the class becomes easier:
- constructors establish it
- methods preserve it
- callers can trust it
This is not only a contract technique. It is a design pattern for stable classes.
28.5 Table-Driven Dispatch
Sometimes a program chooses behavior based on a small set of known keys. A map can express that relationship more clearly than a long chain of if statements.
Conceptually:
nex> let prices := {"apple": 3, "orange": 4, "pear": 5}
nex> prices.get("orange")
4
Instead of:
if item = "apple" then
price := 3
elseif item = "orange" then
price := 4
elseif item = "pear" then
price := 5
end
this pattern stores the association directly as data.
The same idea appears in:
- menus
- small lookup tables
- configuration-driven behavior
Data is often clearer than branching.
28.6 Result Objects Instead of Exceptions
When failure is expected and common, returning a Result[V] can be better than raising:
nex> function safe_divide(a, b: Real): Result[Real]
do
if b = 0.0 then
result := create Result[Real].failure("division by zero")
else
result := create Result[Real].success(a / b)
end
end
This pattern makes the two outcomes explicit:
- success with a value
- failure with an explanation
It is especially useful when callers are expected to branch on the outcome routinely.
28.7 Wrapper at the Boundary
When using files, network access, or imported platform code, wrap the external behavior in a small class or routine with a clear contract.
The pattern is:
- boundary wrapper outside
- portable core logic inside
For example:
File_Readergets text from the environmentword_frequenciescounts words in plain Nex logic
This keeps the unreliable world at the edge and the reasoning-heavy code at the center.
28.8 A Worked Example: Combining Patterns
Here is a simple word-report routine:
nex> function most_frequent_word(text: String): String
require
not_empty: text.length > 0
do
let freq := {}
let words := text.to_lower.split(" ")
across words as word do
let count := freq.try_get(word, 0)
freq.put(word, count + 1)
end
result := freq.keys.get(0)
across freq.keys as word do
if freq.get(word) > freq.get(result) then
result := word
end
end
end
This small routine combines several patterns:
- accumulation into a map
- table-driven counting
- maximum search
- a precondition to exclude the meaningless empty case
Many real programs feel complicated only until you notice that they are simply a clean composition of two or three such patterns.
28.9 Summary
- Patterns are reusable shapes of solution, not rigid formulas
- Accumulator loops summarize processed input
- Search loops stop as soon as the answer is known
- Recursive data often calls for recursive routines
- Good classes are organized around strong invariants
- Tables are often clearer than long condition chains
- Wrappers isolate system boundaries from core logic
- Recognizing patterns reduces design effort and improves clarity
28.10 Exercises
1. Identify which pattern from this chapter best describes each of these routines: max_of, contains, word_frequencies, count_nodes.
2. Rewrite a long if ... elseif ... else chain from one of your earlier examples as a table-driven lookup where possible.
3. Take a class of your own and write its core invariant in one sentence. Then inspect whether its methods really preserve that invariant.
4. Write a routine that counts how many strings in an array begin with a given prefix. Which pattern does it use?
5.* Choose a larger problem, such as processing a grade book or inventory list, and describe which two or three patterns from this chapter you would combine to solve it.