My insertion sort version I'd like to know what you think of my insertion sort version. I tried to be pythonic and avoid while loops with &quot;ugly&quot; index-management:
def sort(a):
    for i, x in enumerate(a):
        for j, y in enumerate(a):
            if y &gt;= x:
                a.insert(j, a.pop(i))
                break

On lists of 1000 random numbers, it seems to be about four times faster (23 ms vs 96 ms) than the implementation from the top-voted answer for the top result for searching [python] insertion sort.
Benchmark code:
from random import random
from time import perf_counter as timer
from statistics import median

n = 1000
repeat = 50

def insertionSort(lst):
    for index in range(1, len(lst)):

        currentvalue = lst[index]
        position = index

        while position &gt; 0 and lst[position - 1] &gt; currentvalue:
            lst[position] = lst[position - 1]
            position = position - 1

        lst[position] = currentvalue

def sort(a):
    for i, x in enumerate(a):
        for j, y in enumerate(a):
            if y &gt;= x:
                a.insert(j, a.pop(i))
                break

solutions = insertionSort, sort

for r in range(1, 6):
    print('Round %d:' % r, end='')
    a = [random() for _ in range(n)]
    for solution in solutions:
        times = []
        for _ in range(repeat):
            copy = a.copy()
            t0 = timer()
            solution(copy)
            times.append(timer() - t0)
            assert copy == sorted(a)
        print('   %6.2f ms' % (median(times) * 1e3), end='')
    print()
Code review
You should use better variable names then a, x and y. But otherwise since your code works your code is fine.
Performance
It seems a couple of users are confused why Python has strange performance.
Enumerate vs index
This is pretty simple both enumerate and index have the same time and space complexity.
If we have a list of False and set one to True and want to find the index of that, both will run in \$O(n)\$ time. It may seem like enumerate is \$O(1)\$ however it is the same as zip(range(len(sequence)), sequence) and we know range is \$O(n)\$.
The difference in speed that we can see is because index is just faster than enumerate.
Your insertion sort vs Grajdeanu Alex's
This comes down to Python being slower than C. If we look at the core of Grajdeanu's solution:

currentvalue = lst[index]
position = index

while position &gt; 0 and lst[position - 1] &gt; currentvalue:
    lst[position] = lst[position - 1]
    position = position - 1

lst[position] = currentvalue


This is doing two things:

Finding the index to stop iterating to:

while position &gt; 0 and lst[position - 1] &gt; currentvalue:



Performing an optimized version of insert and pop. This is as they only touch a subset of the array, but insert and pop touch the entire array, worst case. (Python lists are arrays in the backend.)


If you were to translate Grajdeanu Alex's solution into C the code would out perform your insert and pop.
Bisecting
There's a nice property about insertion sort, as you're iterating through the data everything before your index is sorted. This means we can use a better algorithm to find where to insert into.
We can use the strategy you use in the Guess a Number Between 1-100. By halving the amount of the list we have to search each check we can find where to insert into in \$O(\log(n))\$ time. This is faster than than the \$O(n)\$ that your enumerate and Grajdeanu's algorithms are running in.
There is a library for this, bisect, and most of the legwork is in C too, so it's nice and fast.
My timings
My code to get the timings:


import time
import math
import random
import copy
import bisect

import matplotlib.pyplot as plt
import numpy as np

from graphtimer import flat, Plotter, TimerNamespace


class Iteration(TimerNamespace):
    def test_baseline(data):
        pass

    def test_iterate(data):
        for value in data:
            pass

    def test_enumerate_list(data):
        for item in list(enumerate(data)):
            pass

    def test_enumerate_partial(data):
        for item in enumerate(data):
            pass

    def test_enumerate(data):
        for i, value in enumerate(data):
            pass


class Insertion(TimerNamespace):
    def test_baseline(data, i, value_i, j, value_j):
        pass

    def test_plain(data, i, value_i, j, value_j):
        data.insert(j, data.pop(i))

    def test_index(data, i, value_i, j, value_j):
        data.insert(data.index(value_j), data.pop(i))

    def test_python(data, i, value_i, j, value_j):
        while i &lt; j:
            data[j] = data[j - 1]
            j -= 1
        data[j] = value_i


class Joined(TimerNamespace):
    def test_enumerate_plain(data, i, value_i, j, value_j):
        for j, value_j in enumerate(data):
            if value_i &lt;= value_j:
                data.insert(j, data.pop(i))

    def test_enumerate_index(data, i, value_i, j, value_j):
        for j, value_j in enumerate(data):
            if value_i &lt;= value_j:
                data.insert(data.index(value_j), data.pop(i))

    def test_iterate_index(data, i, value_i, j, value_j):
        for value_j in data:
            if value_i &lt;= value_j:
                data.insert(data.index(value_j), data.pop(i))
                break

class Sorts(TimerNamespace):
    def test_manuel_base(a):
        for i, x in enumerate(a):
            for j, y in enumerate(a):
                if y &gt;= x:
                    a.insert(j, a.pop(i))
                    break

    def test_manuel_insert(a):
        for i, x in enumerate(a):
            for y in a:
                if y &gt;= x:
                    a.insert(a.index(y), a.pop(i))
                    break

    def test_other(lst):
        for index in range(1, len(lst)):
            currentvalue = lst[index]
            position = index
            while position &gt; 0 and lst[position - 1] &gt; currentvalue:
                lst[position] = lst[position - 1]
                position = position - 1
            lst[position] = currentvalue

    def test_peilon(lst):
        output = []
        for item in lst:
            bisect.insort(output, item)


memoize = {}


def create_args(size, *, _i):
    size = int(size)
    key = size, _i
    if key in memoize:
        return copy.deepcopy(memoize[key])
    array = random_array(size)
    j = random.randrange(0, size)
    array[:j] = sorted(array[:j])
    i = 0
    while array[i] &lt; array[j]:
        i += 1
    output = array, i, array[i], j, array[j]
    memoize[key] = output
    return output


def random_array(size):
    array = list(range(int(size)))
    random.shuffle(array)
    return array


def main():
    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True, subplot_kw=dict(xscale='log', yscale='log'))
    axis = [
        (Iteration, {'args_conv': lambda i: [None]*int(i)}),
        (Insertion, {'args_conv': create_args, 'stmt': 'fn(args[0].copy(), *args[1:])'}),
        (Joined, {'args_conv': create_args, 'stmt': 'fn(args[0].copy(), *args[1:])'}),
        (Sorts, {'args_conv': random_array, 'stmt': 'fn(args[0].copy(), *args[1:])'}),
    ]
    for graph, (plot, kwargs) in zip(iter(flat(axs)), axis):
        (
            Plotter(plot)
                .repeat(10, 2, np.logspace(1, 4), **kwargs)
                .min()
                .plot(graph, title=plot.__name__)
        )
    plt.show()

if __name__ == '__main__':
    main()



(click to expand)

Iteration

test_baseline
The timings are flat as they are the time it takes to run the test suit. When determining the performance of each function we need to see how far away from the baseline it is.

test_enumerate &amp; test_enumerate_partial
These are roughly the same and so we know that enumerate, and not tuple unpacking, is the factor at play that is taking up a lot of performance.

test_enumerate, test_enumerate_list &amp; test_iterate
We can see adding more \$O(n)\$ operations makes the code slower. However enumerate is a pretty slow function.


In all enumerate is slow.
Insertion

test_baseline
Since we are copying the data in the test suit we see that at times the other functions are running the fastest that they can.
This is to be expected as we are running tests on a partially sorted array. Ranging from no sort to fully sorted.

test_plain
We can see that data.insert(j, data.pop(i)) is really fast and is consistently around test_baseline. This means if enumerate was faster than data.index then the other answer would not be true.

test_index &amp; test_python
From the areas we can see that optimized Python runs significantly slower than Python's C methods.
This is to be expected, Python is slow.


Joined
These merge the above two together to show the impact of the difference in timings.
These are a single insertion of a full insertion sort.
Unsurprisingly given the previous timings test_enumerate_plain is by far the slowest.
Sorts
This shows that whilst your changes are fast, my answer from '17 is a pretty darn fast insertion sort.
Complexity vs Performance
It should be apparent that in Python these are two entirely different metrics. Complexity is more important when playing on a level playing field, which isn't the case in Python.
But just because Python isn't a level playing field doesn't make it useless. When programming if you try to get the best performance complexity wise then you'll have a good baseline to then optimize from. From here you can then focus on performance which is harder to reason with and harder to compare. And worst case converting the code into C will be far easier.