There are two reasons why TFPList is so fast, the first one is cache locality. Chasing pointers is expensive. The second and more interesting one is geometric growth.
Normally expanding an array requires allocating new memory and copying everything over. Meaning n insertions require O(n²) operations. When using geometric growth you always double the number of allocated elements. So let's say you have an empty list, you add the first element, it starts by allocating space for 4 elements. When adding the 5th element, the space will be doubled to 8 elements, then to 16, etc.
Looking at the runtime, you only need to copy the elements when you do the expansion, in all other cases the runtime is just O(1). So most allocations are practically free. You only have the cost of copying n elements on expansion, which happens every log2(n) steps, which is expensive for that step. But looking at the average runtime over the whole lifetime (
Armortized Analysis) you can do the math and end up at O(n).
So looking at each insertion individually, you have a high likelyhood that it is basically free because the memory is already allocated (probability of n/log2(n)). If you have an algorithm doing a lot of insertions in sequence, the total runtime is just O(n).
An additional thing is the warmup, if you have an algorithm that adds a bunch of stuff, removes some, and adds more, most likely it will run into a stable state, where the size of the list does not change anymore, because not enough elements are removed or no more are added. At this point the operations are basically in O(1).
Compared to Linked Lists, where all operations are in O(n), geometricly growing lists have no disadvantage