Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For indexed access and sequential scans, arrays and dynamic arrays such as C++ std::vector and Java ArrayList usually run faster than linked lists on modern computers. Their contiguous storage works well with CPU caches and prefetching. A linked list can be the better fit when you already have the target position and need frequent insertions or removals there, or when stable references or iterators are essential.

Why Big-O does not tell the whole performance story

Complexity describes how an operation grows with the number of elements, but not how much time it takes on a particular machine. Modern processors move data in cache-line-sized chunks. With contiguous storage, nearby array elements are likely to be fetched together, and hardware prefetching can prepare data the program is about to read. Android Developers describes the benefit this way: “Because the CPU loads an entire cache line, accessing the next element in an array is almost ‘free’ if it’s already in the cache line.” Intel documents 64-byte cache-line granularity and recommends improving locality and reducing the working set to limit cache and TLB costs.

A linked list instead stores elements in separate nodes connected by pointers. Following those pointers can take the program to scattered locations in memory. Each step may require another memory access, and a cache miss—or, in some cases, a page fault—can make that step costly. Microsoft Learn warns that dynamically allocated linked lists can reduce program performance; the University of Michigan likewise notes that array-based implementations generally benefit from using consecutive memory.

This is why two operations with similar theoretical complexity can have different real-world costs. Pointer chasing, cache misses, allocation, and the amount of data moved all affect elapsed time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How the common operations compare

The table compares typical dynamic arrays (std::vector and ArrayList) with a doubly linked list such as C++ std::list. Complexity describes growth as the collection grows; it is not a promise about which operation wins for every size or workload.

Operation Dynamic array Linked list What matters in practice
Access by index O(1) O(n) to walk to the element Arrays support direct indexing; a list must follow links from an end.
Sequential scan O(n) O(n) Arrays usually have the locality advantage because consecutive elements are stored together.
Append at the end Amortized O(1); a capacity expansion can require reallocation and moving elements O(1) when the list maintains an end pointer Appending to a vector is usually efficient, but occasional growth can be costly. Reserving capacity can avoid some reallocations.
Insert or remove at the front O(n), because existing elements must shift O(1) once the relevant end position is available Frequent front edits are a potential list use case, though allocation and traversal still matter.
Insert or remove in the middle O(n) to shift elements after the position O(1) when the target iterator or position is already known; O(n) to find it by traversal The list’s constant-time mutation guarantee does not include locating an unknown position.
Memory overhead Stores elements contiguously, with possible unused capacity Each node also needs link pointers and may incur allocation overhead The exact difference depends on implementation, allocator, element type, and capacity.

These are the container guarantees described for std::vector and std::list in cppreference. In particular, std::vector provides constant-time random access, amortized constant-time insertion or removal at the end, and linear-time insertion or removal elsewhere. std::list does not support fast random access, but insertion and removal at a supplied position are constant time.

When a linked list can make sense

  • You already have the position. If an algorithm holds a valid list iterator or node reference and repeatedly inserts or removes at that location, it can avoid the array’s element shifting. If it must search from the head to find each location, that traversal can erase the advantage.
  • Stable references or iterators are a requirement. Array growth or edits can invalidate references, pointers, or iterators depending on the language and operation. A linked list may preserve references to unaffected nodes through insertions and removals, but the exact guarantees depend on the language and container; check the relevant API before relying on them.
  • Edits dominate access. A workload with frequent local mutations and little indexing or full traversal is more promising for a list than one dominated by scans or random access.

These advantages are conditional, not proof that a list will be faster overall. The list still pays for node allocation, extra link storage, and pointer-based traversal.

When an array or dynamic array is the better fit

  • Code frequently reads by index. A dynamic array provides direct O(1) indexed access; a linked list must walk to the requested element.
  • Code scans most or all elements. Both structures take O(n) steps, but contiguous storage generally lets arrays use cache lines and prefetching more effectively.
  • Appending is common and the final size is predictable. A vector’s append is amortized O(1). In C++, calling reserve when the needed capacity is known or can be estimated can prevent some growth reallocations.
  • Elements are expensive to move or copy. Shifting array elements for a middle edit can be costly when elements are large or have expensive move/copy behavior. That does not automatically favor a list: node allocation and scattered access have costs too, so compare the actual element type and operation mix.

How to choose for a real workload

  1. Describe the operations. Estimate how often the program indexes, scans, appends, and inserts or removes at the front or middle. Separate finding a position from mutating at that position.
  2. Check access and stability requirements. If the program needs random indexing, or must retain references across edits, check whether each candidate container’s guarantees meet the requirement.
  3. Consider the data and memory behavior. Element size and move cost, collection size relative to cache, allocator behavior, and node placement can change the result. A linked list’s theoretical mutation advantage may not compensate for scattered nodes or repeated lookups.
  4. Benchmark representative work. Measure the real language runtime, allocator, element type, data-set size, and operation distribution. A traversal-only benchmark does not answer which structure wins when the program also searches, inserts, and deletes.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How to benchmark without overgeneralizing

There is no universal “array is X times faster” figure that applies to modern computers. Results depend on CPU cache sizes and memory hierarchy, compiler or JIT, allocator, node placement, collection length, element type, and the mix of operations.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a benchmark others can interpret, report the hardware, operating system and runtime versions, compiler flags, allocator, data size, warm-up policy, and operation distribution. State whether the measurement covers traversal, lookup, insertion, deletion, or a combination. When available, include cache-miss or memory-bandwidth counters alongside elapsed time. Keep the conclusion specific to the tested workload rather than presenting one result as a rule for every machine.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.