Amortized Analysis
debt(d7/e3/b3/t6)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints explicitly state automated detection is 'no'. Misapplying amortized analysis (e.g., claiming O(1) when it's O(n), or using amortized-O(1) structures in real-time paths) cannot be caught by linters, SAST tools, or compilers. It requires careful code review or runtime profiling/benchmarking to identify the mismatch between claimed and actual complexity behavior.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates measuring average cost over N operations and choosing the right data structure. Fixing a misuse typically means swapping one data structure for another (e.g., replacing array_shift with SplQueue, or switching from a dynamic array to a pre-allocated one in real-time code). This is a localized fix touching a few call sites, not an architectural rework.
Closest to 'localised tax' (b3). This is a conceptual/analytical tool that applies to specific data structure choices rather than pervading the entire codebase. The applies_to contexts (web, cli) are broad, but the actual impact of understanding amortized analysis is localized to performance-critical sections. It doesn't shape the system's architecture — it informs specific implementation choices in isolated components.
Closest to 'notable trap' (t5), +1 to t6. The misconception is clear and significant: developers frequently assume amortized O(1) means every operation is O(1). This is a well-documented gotcha that most developers eventually learn, but it contradicts the intuitive reading of 'O(1)' and causes real problems in latency-sensitive code. The additional common mistakes (not knowing array_shift is O(n), assuming SplStack vs array equivalence) compound the confusion. Slightly worse than a standard t5 because the 'O(1)' label actively misleads — it looks like a worst-case guarantee to anyone who hasn't specifically studied amortized analysis.
Also Known As
TL;DR
Explanation
Some operations are occasionally expensive but rarely so. Amortized analysis spreads the cost: a dynamic array doubles capacity when full — a resize is O(n) but happens so rarely that over n appends the average is O(1). The accounting method assigns 'credits' from cheap operations to pay for future expensive ones. PHP's array[] = $item is amortized O(1) for the same reason. Amortized != average: average considers random inputs; amortized considers worst-case sequences. The difference matters when budgeting for occasional spikes.
Common Misconception
Why It Matters
Common Mistakes
- Confusing amortized O(1) with worst-case O(1) — occasional spikes still happen.
- Using dynamic arrays in hard real-time systems — the O(n) resize spike causes deadline misses.
- Not understanding that PHP unshift/shift are O(n) NOT O(1) amortized — they reindex every time.
- Assuming stack operations are always cheap — SplStack push/pop are O(1) amortized, but array_shift is O(n).
Avoid When
- Do not use amortized O(1) as a guarantee for real-time or latency-sensitive systems — the occasional O(n) resize still happens.
- Avoid citing amortized cost when your workload triggers worst-case operations systematically (e.g. always inserting at max-load).
When To Use
- When evaluating data structures whose worst-case single operation cost is misleading (dynamic arrays, hash table rehashing, splay trees).
- When choosing between two structures with similar average performance — amortized cost reveals the true cost per operation over a sequence.
- When documenting API complexity guarantees to callers who care about throughput rather than single-call latency.
Code Examples
// Mistaking amortized for guaranteed:
function realtimeTask(SplDoublyLinkedList $queue): void {
// In a 16ms real-time frame:
$queue->push($data); // Usually O(1)... but occasionally O(n) on resize
// Frame budget exceeded! Dropped frame in a game/audio context
}
// Understand what 'amortized O(1)' means:
// For N total pushes to a dynamic array:
// - ~N pushes are O(1)
// - log(N) resizes are O(N) each
// Total: O(N) for N operations = O(1) amortized
// Pre-allocate to avoid all resize overhead:
$spl = new SplFixedArray(10000); // O(1) guaranteed access, no resize
for ($i = 0; $i < 10000; $i++) {
$spl[$i] = $data[$i]; // Always O(1) — no resize possible
}