03 · Dynamic storage
Choose a container by access pattern, lifetime, and ownership.
Dynamic arrays, queues, and associative arrays all grow at run time, but they solve different problems and have different copying, ordering, and missing-entry semantics.
Access-pattern selector
Three dynamic containers, three contracts
- 01Dynamic arraynew[N]
Indexed collection; explicit allocation and resize.
- 02Queue[$]
Ordered sequence with push/pop at the ends.
- 03Associative array[key_t]
Sparse map with key-based lookup and ordered traversal rules.
Indexed collection; explicit allocation and resize.
- Resize
- new[N](old)
- Copies the retained prefix; allocation without old discards prior contents.
- Queue bound
- [$:max]
- A bounded queue exposes an architectural capacity.
- Traversal
- first(ref key)
- Returns success and writes the key through its argument.
Container selection is part of the model's algorithm and resource contract, not a cosmetic syntax preference.
Observe
Measure how data is inserted, removed, indexed, traversed, resized, and passed across subroutine boundaries.
Decide
Select the narrowest container whose methods match the dominant operation, then define bounds and failure handling.
Failure
An unbounded queue hides backpressure, a resized dynamic array silently loses elements, and an associative traversal that ignores the success return can use a stale key.
Prove
Assert capacity, check every method return, test empty and missing-key cases, and verify whether copied elements are values or shared object handles.
Allocation and resizing are observable operations
A dynamic array must be allocated before indexed use. Resizing with new_size and an optional source preserves only the overlapping prefix. Queue operations shift logical positions, while associative arrays allocate entries on demand.
int dyn[];
int q[$:7];
int score[string];
string key;
dyn = new[4];
foreach (dyn[i]) dyn[i] = i * 10;
dyn = new[6](dyn); // keep first four values
if (q.size() < 8) q.push_back(42);
if (!score.exists("pkt7")) score["pkt7"] = 0;
if (score.first(key)) begin // return is success; key is an output
do $display("%s=%0d", key, score[key]);
while (score.next(key));
endCopying a container does not deep-copy its object graph
Pass-by-value copies the container value. For integral elements that means independent data; for class-handle elements it copies handles, so both containers still refer to the same objects. Pass by ref when the caller's container shape must change, and clone elements when independent objects are required.
| Need | Best starting point | Boundary to review |
|---|---|---|
| Known compile-time size | Fixed unpacked array | Range direction |
| Runtime size, random access | Dynamic array | Resize and allocation |
| FIFO/deque behavior | Queue | Bound and empty access |
| Sparse numeric or string keys | Associative array | exists and traversal |
| Shared mutable objects | Any handle container | Clone versus alias |
Methods return information—do not throw it away
delete, size, insert, push, pop, exists, first, last, next, and prev encode the container's state transitions. A pop on an empty queue or use of a missing associative entry should be handled as a protocol decision, not left to an accidental default.
- foreach follows the declared index order; do not assume numeric ascending order for every declaration.
- Associative iteration order is defined by the index type's ordering, not insertion order.
- Wildcard-index associative arrays reduce type safety and portability; prefer a specific key type.
- A queue is not a synchronization primitive—concurrent producers still need a mailbox, semaphore, or explicit protocol.
Explain it out loud
Interview reasoning checkpoints
Use a dynamic array for runtime-sized indexed storage, a queue for ordered insertion and removal, and an associative array for sparse key lookup. Then constrain capacity and define empty or missing access.
- Start from the dominant access operation, not just whether size changes.
- Account for allocation, ordering, and traversal guarantees.
- Verify the worst-case memory and backpressure behavior.
Good answers connect the language feature to algorithmic complexity and resource intent.
Using an unbounded queue for every dynamic collection.
It copies the queue container, but class handles inside are still aliases to the same objects. A deep copy requires cloning each referenced object.
- Separate container storage from element representation.
- Integral elements are values; class variables are handles.
- Choose pass-by-reference only when the callee should change the caller's queue itself.
The expected distinction is shallow container copy versus deep object copy.
Saying either “everything aliases” or “everything is independent.”
