Design Patterns «Prev Next»

Big O Notation and Design Trade-offs

Big O notation describes an asymptotic upper bound on how a resource requirement grows as the input size, n, increases. The resource is usually execution time or memory. Big O deliberately ignores constant factors and lower-order terms so that two approaches can be compared independently of a particular processor or test run.

If an operation takes 3n + 20 steps, its growth is O(n). If it takes n2 + 5n steps, its growth is O(n2). This does not predict an exact duration; measurement is still required to understand real performance.

Common Growth Rates

ComplexityNameRepresentative operation
O(1)ConstantReading an array element by index
O(log n)LogarithmicBinary search in a sorted array
O(n)LinearScanning every element once
O(n log n)LinearithmicMerge sort or heap sort
O(n2)QuadraticComparing every pair of elements
O(2n)ExponentialExploring every subset in a direct solution
O(n!)FactorialEnumerating every permutation
Comparison of common algorithmic growth rates
As input grows, higher-order functions consume resources much more rapidly.

Case Analysis Is Separate from the Bound

Best, average, and worst case describe which inputs are being considered. Big O describes a bound. They are related but not interchangeable. For example, a linear search has a best case of O(1) when the first element matches and a worst case of O(n) when the item is last or absent.

Average-case claims require an assumption about the distribution of inputs. Without that model, “average” is not a meaningful guarantee. A worst-case upper bound is often useful because it states what the operation will not exceed asymptotically.

Java Examples

Linear scan: O(n)

static boolean contains(int[] values, int target) {
    for (int value : values) {
        if (value == target) {
            return true;
        }
    }
    return false;
}

Pair comparison: O(n2)

static int countEqualPairs(int[] values) {
    int pairs = 0;
    for (int i = 0; i < values.length; i++) {
        for (int j = i + 1; j < values.length; j++) {
            if (values[i] == values[j]) {
                pairs++;
            }
        }
    }
    return pairs;
}

Amortized Analysis

One operation may occasionally be expensive while a sequence remains efficient. Appending to a dynamic array is normally constant time, but a resize copies the existing elements. Across many appends, the total work is linear, giving an amortized O(1) cost per append. Amortized analysis is a guarantee over a sequence of operations; it is not the same as average-case analysis over random inputs.

Where Complexity Meets Design Patterns

A design pattern is judged primarily by responsibilities, coupling, and changeability, but it can also alter runtime behavior. Observer notification is typically proportional to the number of registered observers. A naïve Composite traversal visits every node. Flyweight may reduce memory while adding lookup work. Decorator adds a delegation layer for each wrapper.

Big O alone cannot decide whether a pattern is appropriate. Use it to identify scaling risks, then measure representative workloads and weigh the result against clarity, flexibility, and correctness.

Data Structures and Algorithms

SEMrush Software 1 SEMrush Banner 1