I tested s.replace('a', '') where s is a string of two million 'a' and potentially a single outlier 'b' at start, middle or end. That single outlier made it much slower:
At TIO with their Python 3.8 pre-release version:
a = 'a' * 10**6; s = f'{a}{a}' 5 ms 5 ms 5 ms
a = 'a' * 10**6; s = f'b{a}{a}' 24 ms 24 ms 24 ms
a = 'a' * 10**6; s = f'{a}b{a}' 25 ms 25 ms 25 ms
a = 'a' * 10**6; s = f'{a}{a}b' 25 ms 25 ms 25 ms
a = 'a' * 10**6; s = f'b{a}b{a}b' 25 ms 25 ms 25 ms
On my old laptop with Python 3.10:
a = 'a' * 10**6; s = f'{a}{a}' 4 ms 4 ms 4 ms
a = 'a' * 10**6; s = f'b{a}{a}' 93 ms 94 ms 95 ms
a = 'a' * 10**6; s = f'{a}b{a}' 94 ms 95 ms 95 ms
a = 'a' * 10**6; s = f'{a}{a}b' 94 ms 94 ms 95 ms
a = 'a' * 10**6; s = f'b{a}b{a}b' 95 ms 95 ms 96 ms
How does that single outlier 'b' make it so much slower?
Full benchmark code:
from timeit import repeat
setups = [
"a = 'a' * 10**6; s = f'{a}{a}'",
"a = 'a' * 10**6; s = f'b{a}{a}'",
"a = 'a' * 10**6; s = f'{a}b{a}'",
"a = 'a' * 10**6; s = f'{a}{a}b'",
"a = 'a' * 10**6; s = f'b{a}b{a}b'",
]
for _ in range(3):
for setup in setups:
times = repeat("s.replace('a', '')", setup, number=1)
print(f'{setup:{max(map(len, setups))}}',
*('%3d ms ' % (t * 1e3) for t in sorted(times)[:3]))
print()