liveBook · Manning

Worst-case performance

As explained in Fabulous Adventures in Data Structures and Algorithms

Worst-case performance is the resource usage or execution behavior of an algorithm or data structure on its least favorable inputs.

Worst-case performance describes the time, memory, or other resources an algorithm may use on its least favorable input of a stated size. Say what size means: a tree operation might depend on tree height rather than node count. Also say which resource and which operation the bound describes; a time bound does not describe extra memory. section 1.1

A costly dequeue

An immutable queue can use one stack for enqueues and another for dequeues. Most dequeues pop an item in O(1) time. When the dequeue stack runs out, reversing an enqueue stack of n items takes O(n) time and space. That is the worst case for one Dequeue, even though Enqueue takes constant time and memory. section 2.4

Does that make the queue slow overall? Not necessarily. Each item is pushed and popped only a constant number of times on its way through the queue, so enqueuing and eventually dequeuing items has O(1) amortized cost per item. The expensive reversal is followed by cheap dequeues—provided those calls continue from the resulting queue state. section 2.4 section 2.6.3

When the expensive call repeats

Enqueue 100 items, dequeue once, then undo to the earlier queue state. Repeating the dequeue reverses the same 99-item stack again. Undo prevents the later cheap dequeues from paying for that work. If this matters in practice, options include a data structure with a cheaper worst case or memoizing the reversal at a memory cost. section 2.6.3

Worst-case bounds also expose costs hidden by an algorithm’s apparent simplicity:

  • A recursive change-ringing generator changes one pair between permutations, yet enumerating one n-element result can make up to n nested calls per element: O(n²) worst-case time. An O(log n) immutable-deque InsertAt could reduce this to O(n log n) per permutation. section 6.2.4
  • Generating the next lexicographic permutation can require O(n) comparisons and swaps in the worst case. Most calls are much cheaper; across all permutations of unique items, the amortized cost is O(1) per permutation. section 6.2.1

A better asymptotic worst case is not automatically the better practical choice. A naïve string search with an O(n²) worst case can still be fast for the short searches typical of its intended use. Check the expected input sizes and common cases alongside the bound. section 1.1

Listing 6.12 A recursive change-ringing algorithm
static IEnumerable<IEnumerable<int>> Changes(int n)
{
    if (n == 0) #1
    {
        yield return []; 
        yield break;
    }
    bool fromLeft = false; #2
    foreach (var perm in Changes(n - 1))
    {
        for (int row = 0; row < n; row += 1)
            yield return perm.InsertAt(fromLeft ? row : n-row-1, n-1);
        fromLeft = !fromLeft; #3
    }
}

static class Extensions
{
    public static IEnumerable<T> InsertAt<T>(
        this IEnumerable<T> items, int index, T insert)
    {
        int current = 0;
        foreach (T item in items)
        {
            if (current == index)
                yield return insert;
            yield return item;
            current += 1;
        }
        if (current == index)
            yield return insert;
    }
}

The C# change-ringing generator uses recursive permutations and InsertAt; enumerating the nested sequences reveals why producing one result can cost O(n²). section 6.2.4

How Fabulous Adventures in Data Structures and Algorithms covers it

How deep it goes
Works through operation-level time and space arguments, including repeated costs under undo; defers deeper mitigation of persistent-data-structure worst cases.
Examples
Compares stack reversal in an immutable queue, repeated dequeue after undo, and nested calls in a recursive permutation generator.
What it assumes
Assumes familiarity with C# collection operations and introduces the amortized queue argument by counting pushes and pops.
What you can do after
Evaluate/understand it by locating expensive operations, stating their resource bounds, and checking whether common or repeated calls change the practical trade-off.
How it connects
Connects worst-case performance to Big-O notation, amortized analysis, persistent data structures, permutation generation, and memory complexity.

Also explained in

Compare how 3 books explain worst-case performance →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Fabulous Adventures in Data Structures and Algorithms. The section links point to the parts of the book it draws on.