../

Lists

list in Python 3.14: creating, indexing and slicing, every method, slice assignment, searching, sorting, copying and aliasing, costs, the idioms DS&A solutions are built from, typing, and when another container fits better. Comprehensions are in Fundamentals; the TypeScript side is in Arrays.

Creating

ExpressionResultNotes
[], list()[]empty; the literal is a little faster
[1, "a", None]mixed items are allowedannotate: mypy infers list[object], pyright list[int | str | None]
list("abc")['a', 'b', 'c']list(iterable) drains anything iterable
list(range(5)), list(range(10, 0, -3))[0, 1, 2, 3, 4], [10, 7, 4, 1]range alone is a lazy sequence; keep it lazy if you only loop
list({"a": 1, "b": 2})['a', 'b']a dict iterates its keys; list(d.items()) for pairs
"a b c".split()['a', 'b', 'c']from text; see Strings
[0] * 3[0, 0, 0]repeats references: safe only for immutable items
[[] for _ in range(3)][[], [], []]three separate inner lists
[*a, *b], [*range(3), 9]concatenationunpacks any iterables, not just lists
[x * 2 for x in xs if x]comprehensionmap + filter in one expression
list(map(f, xs)), list(gen())materialize an iteratormap, filter, zip and generators are lazy
sorted(s)new sorted listfrom any iterable: set, dict, generator
grid = [[0] * 3 for _ in range(2)]  # 2 rows, 3 columns
grid[0][0] = 1
print(grid)  # [[1, 0, 0], [0, 0, 0]]
 
bad = [[0] * 3] * 2  # the same row object, twice
bad[0][0] = 1
print(bad)  # [[1, 0, 0], [1, 0, 0]]

Indexing & slicing

xs[start:stop:step]: stop is excluded, negatives count from the end, and a slice clamps out-of-range bounds and always returns a new list. A single index out of range raises IndexError; there is no undefined.

Expressionlist("python") givesJS equivalent
xs[0], xs[-1]'p', 'n'xs[0], xs.at(-1)
xs[1:4]['y', 't', 'h']xs.slice(1, 4)
xs[:2], xs[2:]['p', 'y'], ['t', 'h', 'o', 'n']xs.slice(0, 2), xs.slice(2)
xs[-3:], xs[:-1]['h', 'o', 'n'], ['p', 'y', 't', 'h', 'o']xs.slice(-3), xs.slice(0, -1)
xs[::2]['p', 't', 'o']none
xs[::-1]['n', 'o', 'h', 't', 'y', 'p']xs.toReversed()
xs[3:0:-1]['h', 't', 'y']none: with a negative step, start is included and stop excluded
xs[:]shallow copyxs.slice(), [...xs]
xs[10:][]xs.slice(10)
xs[10]IndexErrorundefined
xs = [10, 20, 30, 40, 50]
evens = slice(None, None, 2)  # a reusable, named slice
xs[evens]  # [10, 30, 50]
evens.indices(len(xs))  # (0, 5, 2): resolved bounds
 
first, *rest = xs  # 10 and [20, 30, 40, 50]
*init, last = xs  # [10, 20, 30, 40] and 50
head = xs[0] if xs else None  # no xs.get(i, default)

The starred name is always a list, even when unpacking a tuple or a generator.

Adding, removing & replacing

OperationReturnsNotes
xs.append(x)Noneone item at the end; appending a list nests it
xs.extend(it), xs += itNoneevery item of any iterable: xs += "ab" adds 'a', 'b'
xs.insert(i, x)Nonebefore index i; negatives count from the end, past the end appends
xs[i] = xreplace one; IndexError past the end (no JS-style auto-grow)
xs[a:b] = itemsreplace a range with any number of items
xs[i:i] = itemsinsert several at i (JS splice(i, 0, ...items))
xs[::2] = itemsextended slice: items must have the same length, else ValueError
xs.pop(), xs.pop(i)the removed itemIndexError when empty or out of range
xs.remove(x)Nonefirst item equal to x; ValueError if absent
del xs[i], del xs[a:b], del xs[::2]delete by index or slice
xs.clear(), del xs[:]Noneempty in place: every name for this list sees it
xs + ysnew listboth must be lists: [1] + (2,) is a TypeError, xs += (2,) is fine
xs * n, n * xsnew listn <= 0 gives []
xs = ["a", "d"]
xs.insert(1, "b")  # ['a', 'b', 'd']
xs[2:2] = ["c"]  # insert at 2: ['a', 'b', 'c', 'd']
xs[1:3] = ["X"]  # 2 items become 1: ['a', 'X', 'd']
xs[len(xs) :] = ["e", "f"]  # same as extend
del xs[-2:]  # ['a', 'X', 'd']
xs += "pq"  # ['a', 'X', 'd', 'p', 'q']
xs.pop()  # 'q'
xs.remove("X")  # ['a', 'd', 'p']
 
ys = [0, 1, 2, 3, 4, 5]
ys[::2] = [9, 9, 9]  # [9, 1, 9, 3, 9, 5]
del ys[1::2]  # [9, 9, 9]
 
zs: list[object] = [1, 2]
zs.append([3, 4])  # nests: [1, 2, [3, 4]]
zs.extend([3, 4])  # [1, 2, [3, 4], 3, 4]
xs = [10, 20, 30, 40, 50] 10 20 30 40 50 spare xs.insert(1, 15): open slot [1] 10 20 30 40 50 xs.insert(1, 15): write 15 10 15 20 30 40 50 xs.pop(0) 15 20 30 40 50 spare [0] [1] [2] [3] [4] [5] capacity 6: one spare slot 4 pointers move right: O(n - i) len 6, still capacity 6: no reallocation 10 is returned; 5 pointers move left: O(n) moved pointer written

1/4

xs = [10, 20, 30, 40, 50] is built from a literal with room for 6 pointers, so slot [5] is spare.

Searching & testing

ExpressionReturnsNotes
x in xs, x not in xsboollinear scan; checks identity before ==
xs.index(x)first indexValueError if absent: there is no -1
xs.index(x, start, stop)first index in xs[start:stop]counted from the start of xs
xs.count(x)number of equal items
len(xs)lengthstored, so O(1)
min(xs), max(xs)smallest / largestkey=; ValueError on empty unless default=
sum(xs)totalmath.fsum for accurate floats; sum refuses strings
any(xs), all(xs)boolstop early; any([]) is False, all([]) is True
next((x for x in xs if p(x)), None)first match or the defaultJS find
bisect.bisect_left(xs, x)insertion pointO(log n), sorted lists only
xs == ysboolsame length and items equal pairwise; [1, 2] == (1, 2) is False
xs < ysboollexicographic: first differing item decides, else the shorter list is smaller
xs = [3, 1, 4, 1, 5, 9, 2, 6]
4 in xs  # True
xs.index(1), xs.index(1, 2)  # (1, 3)
xs.count(1)  # 2
max(xs), min(xs, key=lambda n: abs(n - 7))  # (9, 6)
max([], default=None)  # None
next((n for n in xs if n > 4), None)  # 5
max(range(len(xs)), key=xs.__getitem__)  # 5: argmax
[i for i, n in enumerate(xs) if n == 1]  # [1, 3]
 
[1, 2] < [1, 2, 0], [2] > [1, 99]  # (True, True)

Sorting & reversing

CallReturnsMutatesNotes
xs.sort(key=None, reverse=False)Noneyeslists only; arguments are keyword-only
sorted(it, key=None, reverse=False)new listnoany iterable: tuple, set, dict keys, generator
xs.reverse()Noneyesin place
reversed(xs)iteratornolazy; list(reversed(xs)) for a copy
xs[::-1]new listnoreversed copy
random.shuffle(xs)Noneyesrandom.sample(xs, k=len(xs)) for a shuffled copy

Rules that differ from JS:

  • The default order uses < on the items: numbers sort numerically ([10, 9, 1] gives [1, 9, 10]), strings by code point ("Z" < "a"). Mixing int and str raises TypeError.
  • There is no compare function. key=f calls f once per item and sorts by the results; tuples as keys give multi-key sorts. functools.cmp_to_key(cmp) adapts a JS-style (a, b) -> int.
  • The sort is stable (equal keys keep their order), including with reverse=True.
  • Timsort: O(n log n), and close to O(n) on data that is already mostly in order.
from operator import attrgetter, itemgetter
 
words = ["banana", "Cherry", "apple", "fig"]
sorted(words)  # ['Cherry', 'apple', 'banana', 'fig']
sorted(words, key=str.casefold)
# ['apple', 'banana', 'Cherry', 'fig']
sorted(words, key=len, reverse=True)  # stable ties
# ['banana', 'Cherry', 'apple', 'fig']
 
rows = [("ann", 31), ("bo", 25), ("cy", 31)]
sorted(rows, key=itemgetter(1))  # by age
# [('bo', 25), ('ann', 31), ('cy', 31)]
sorted(rows, key=lambda r: (-r[1], r[0]))  # age desc, name
# [('ann', 31), ('cy', 31), ('bo', 25)]
 
nums = [3, None, 1]
sorted(nums, key=lambda n: (n is None, n))  # None last
# [1, 3, None]
 
by = attrgetter("last", "first")  # objects: key=by
KeySorts by
key=str.casefoldcase-insensitive text
key=lenlength
key=itemgetter(1), itemgetter("age", "name")index or dict key; several give a tuple
key=attrgetter("age")attribute; dotted paths like "owner.name" work
key=lambda x: (-x.score, x.name)score descending, then name ascending
key=locale.strxfrmthe current locale's collation (after locale.setlocale)
key=d.__getitem__the values of dict d, when sorting its keys (key=d.get runs too, but type checkers reject it: get may return None)

Iterating & transforming

ToolGivesJS
[f(x) for x in xs if p(x)]new listxs.filter(p).map(f)
map(f, xs), filter(p, xs)lazy, single-use iteratorsxs.map(f), xs.filter(p) (eager arrays)
filter(None, xs)truthy itemsxs.filter(Boolean)
enumerate(xs, start=0)(index, item) pairsxs.entries()
zip(a, b, strict=True)tuples in step; strict raises on unequal lengthsnone
zip(*rows)columns: a transposenone
reversed(xs)items from the end, no copynone
itertools.chain.from_iterable(xss)flatten one levelxs.flat()
itertools.pairwise(xs)(xs[0], xs[1]), (xs[1], xs[2]), ...none
itertools.batched(xs, n)tuples of n; strict=True (3.13+) rejects a short last onenone
itertools.accumulate(xs)running totals; initial=0 prepends a 0none
itertools.groupby(xs, key)runs of equal keys: sort by the key firstObject.groupBy (whole input, not runs)
functools.reduce(f, xs, init)a foldxs.reduce(f, init)
from itertools import accumulate, pairwise
 
names = ["ann", "bo", "cy"]
scores = [90, 72, 85]
 
for rank, name in enumerate(names, start=1):
    print(rank, name)  # 1 ann, 2 bo, 3 cy
 
passed = [n for n, s in zip(names, scores) if s >= 80]
# ['ann', 'cy']
by_name = dict(zip(names, scores, strict=True))
# {'ann': 90, 'bo': 72, 'cy': 85}
list(accumulate([1, 2, 3, 4]))  # [1, 3, 6, 10]
[b - a for a, b in pairwise([1, 4, 9, 16])]  # [3, 5, 7]

Removing items from a list while looping over it skips items, because the loop walks by index:

xs = [1, 2, 2, 3]
for x in xs:
    if x == 2:
        xs.remove(x)  # ❌ skips the item after x
print(xs)  # [1, 2, 3]
 
xs = [1, 2, 2, 3]
xs[:] = [x for x in xs if x != 2]  # ✅ [1, 3], same list
for x in xs.copy():  # ✅ or loop over a copy
    if x == 1:
        xs.remove(x)

Copying & aliasing

Assignment binds a name; it never copies. A function that receives a list receives the caller's list.

ExpressionGivesDepth
ys = xsthe same list under a second namenone
xs.copy(), xs[:], list(xs), [*xs]a new list holding the same itemsshallow
copy.deepcopy(xs)a new list with items copied recursivelydeep
xs.clear() vs xs = []empties the list for everyone vs rebinds only xs
xs += ys vs xs = xs + ysextends in place vs builds a new list
import copy
 
a = [[1, 2], [3]]
b = a  # alias
c = a.copy()  # new outer list, same inner lists
d = copy.deepcopy(a)  # nothing shared
 
a[0].append(99)
a.append([4])
print(b)  # [[1, 2, 99], [3], [4]]
print(c)  # [[1, 2, 99], [3]]
print(d)  # [[1, 2], [3]]
 
xs = [1]
ys = xs
xs += [2]  # in place: ys is [1, 2]
xs = xs + [3]  # new list: ys stays [1, 2]
 
 
def tag(items: list[str]) -> None:
    items.append("seen")  # mutates the caller's list
 
 
names = ["ann"]
tag(names)  # names is ['ann', 'seen']
a = [[1, 2], [3]] b = a c = a.copy() d = copy.deepcopy(a) a[0].append(99) a.append([4]) print(b, c, d) a b c d outer list new outer list 1 2 99 99 3 [1, 2] seen by a, b and c [1, 2, 99] [3] 4 4 shared, not copied 1 2 3 1 2 3 copies: [1, 2] copy: [3] b [[1, 2, 99], [3], [4]] c [[1, 2, 99], [3]] d [[1, 2], [3]]

1/7

a = [[1, 2], [3]] builds an outer list whose two slots point to two inner lists.

Costs & memory

CPython's list is a dynamic array of pointers to objects. When it is full it reallocates with about 12.5% spare room, so appends are amortized O(1); anything that inserts or deletes near the front moves every later pointer.

xs list object length 3 allocated 4 ob_item array of 4 pointer slots [0] [1] [2] [3] spare 7 42 one shared int int object getsizeof(xs) == 88: a 56-byte header plus 4 slots of 8 bytes
xs = [7, 7, 42]: the list stores pointers; the ints are separate objects
xs = [] len 0 capacity 0 getsizeof 56 no pointer array yet: an empty list holds no slots xs.append(1) len 1 capacity 4 getsizeof 88 new block: 4 slots 1 xs.append(2) ... xs.append(4) len 4 capacity 4 getsizeof 88 block: 4 slots 1 2 3 4 xs.append(5) len 5 capacity 8 getsizeof 120 old block: 4 slots, full 1 2 3 4 copied if realloc moves it new block: 8 slots 1 2 3 4 5 xs.append(6) ... xs.append(8) len 8 capacity 8 getsizeof 120 block: 8 slots 1 2 3 4 5 6 7 8 xs.append(9) len 9 capacity 16 getsizeof 184 old block: 8 slots, full 1 2 3 4 5 6 7 8 copied if realloc moves it new block: 16 slots 1 2 3 4 5 6 7 8 9 xs.append(10) ... xs.append(16) len 16 capacity 16 getsizeof 184 block: 16 slots 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 xs.append(17) len 17 capacity 24 getsizeof 248 old block: 16 slots, full 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 copied if realloc moves it new block: 24 slots 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 new item old block spare slot

1/8

xs = [] has length 0 and no pointer array yet: sys.getsizeof(xs) is 56, the header alone.

OperationCostNotes
xs[i], xs[i] = x, len(xs)O(1)
xs.append(x), xs.pop()O(1) amortizedthe end is cheap
xs.insert(i, x), xs.pop(i), del xs[i]O(n − i)items after i shift
xs.insert(0, x), xs.pop(0)O(n)collections.deque does both ends in O(1)
x in xs, index, count, removeO(n)a set or dict answers in in O(1)
xs[a:b], xs.copy()O(b − a), O(n)copies the pointers
xs.extend(ys), xs += ysO(len(ys))
xs + ysO(len(xs) + len(ys))xs = xs + [x] in a loop is O(n²) overall
xs.sort(), sorted(xs)O(n log n)
min, max, sum, xs.reverse()O(n)
bisect.bisect_left / insortO(log n) / O(n)the search is fast, the insert still shifts
heapq.heappush / heappopO(log n)a priority queue on a plain list
import sys
from array import array
 
sys.getsizeof([])  # 56: header only (64-bit CPython)
sys.getsizeof([None] * 3)  # 80: plus 3 pointers of 8 bytes
sys.getsizeof(1.0)  # 24: each float is its own object
 
prices = array("d", [1.5, 2.25])  # packed C doubles
prices.append(3.0)  # 8 bytes per item, no objects

getsizeof never counts the items themselves. Flattening lists with sum(lists, []) is quadratic for the same reason as xs = xs + [x]; use itertools.chain.from_iterable or a comprehension.

NeedUse insteadWhy
a fixed record, a dict key or set membertupleimmutable, and hashable when its items are; see Tuples
a queue, or adding/removing at both endscollections.dequeO(1) appendleft/popleft; maxlen= makes a ring buffer
fast membership, uniquenessset, dict.fromkeys(xs)O(1) average in; dict.fromkeys keeps order; see Sets
lookup by keydictO(1) average by key instead of a scan
the smallest item, repeatedlyheapq on a listO(log n) push and pop; 3.14 adds heappush_max and friends
top n of manyheapq.nlargest(n, xs, key=)no full sort
millions of numbersarray.array or a NumPy ndarraypacked values instead of pointers to objects
a read-only view for callersreturn a tuple, or type it Sequence[T]callers can't mutate your list
a list that validates or reacts to writessubclass collections.UserLista list subclass's extend, insert and += never call your overridden append or __setitem__

DS&A idioms

The list moves that problem solutions are built from, and the trap in each. The patterns themselves, with TypeScript and JavaScript beside Python, are in the DS&A sheets (linked in the last column).

JobPythonWatch outMore
Stackst.append(x), st.pop(), peek st[-1], empty check if st:pop() and st[-1] raise IndexError on an empty stackStacks
Queue, BFSq = deque([start]), q.append(x), q.popleft()never xs.pop(0): O(n) per pop turns BFS into O(n²)Queues & deques
Min-heapheappush(h, (prio, i, item)), heappop(h), peek h[0]tuples compare field by field: on a prio tie the next field decides, so add a counter i before items that can't compareHeaps, heapq
Max-heapheapify_max, heappush_max, heappop_max (3.14)before 3.14: push -x, negate again on popMax-heaps, heapq
Top kheapq.nlargest(k, xs, key=f), nsmallestO(n log k); max/min for k = 1Top-k, heapq
Sorted lookupi = bisect_left(xs, x): first xs[i] >= x; bisect_right: first > xi can be len(xs): test i < len(xs) and xs[i] == x before readingLower & upper bound
Search the answerbisect_left(range(lo, hi), True, key=ok)ok must be monotonic (all False, then all True); the result i is an offset, so the answer is lo + i, and i == hi - lo means none worksOn the answer
Prefix sumspre = [0, *accumulate(xs)], then sum(xs[i:j]) == pre[j] - pre[i]the leading 0 makes ranges starting at 0 workPrefix sums
Two pointersi, j = 0, len(xs) - 1, while i < j:, swap xs[i], xs[j] = xs[j], xs[i]the right side is built first, then targets are assigned left to rightTwo pointers
DP tabledp = [0] * (n + 1), dp = [[0] * (m + 1) for _ in range(n + 1)][[0] * m] * n shares one row between all nBottom-up DP
Sentinelsdist = [math.inf] * n, best = -math.infmath.inf is a float: int(math.inf) raises OverflowErrorNumbers
Countingcnt = [0] * 26, cnt[ord(ch) - ord("a")] += 1, or Counter(s)lists are unhashable: key a dict by tuple(cnt)Group anagrams, dict idioms
Grid neighborsfor dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):, then 0 <= r < R and 0 <= c < Cnegative indexes don't raise: grid[-1] silently reads the last rowGrids as graphs
Intervalsivs.sort() sorts by start, then endkey=lambda iv: iv[0] when ties must keep input orderIntervals
Argsortorder = sorted(range(len(xs)), key=xs.__getitem__)indexes in value order; stable for tiesArgsort
Backtrackingpath.append(x), recurse, path.pop(); record out.append(path[:])out.append(path) records the same list every time, and it ends up emptyBacktracking
Memo keys@cache on ints, strings, tuplesa list argument raises TypeError: unhashable; pass tuple(xs) or an index
Deep recursionan explicit stack: st = [root], while st:the limit is 1,000 frames by default (sys.getrecursionlimit())Depth limits, Graphs
import heapq
from bisect import bisect_left, bisect_right
from itertools import accumulate
 
jobs = [(2, "b"), (1, "a"), (2, "c")]
h: list[tuple[int, int, str]] = []
for i, (prio, name) in enumerate(jobs):
    heapq.heappush(h, (prio, i, name))  # i breaks ties
heapq.heappop(h)  # (1, 1, 'a')
 
xs = [1, 3, 3, 3, 7]
bisect_left(xs, 3), bisect_right(xs, 3)  # (1, 4)
bisect_right(xs, 3) - bisect_left(xs, 3)  # 3 copies of 3
bisect_left(range(100), True, key=lambda m: m * m >= 50)
# 8: the smallest m with m * m >= 50
 
pre = [0, *accumulate([2, 4, 1, 5])]  # [0, 2, 6, 7, 12]
pre[3] - pre[1]  # 5: items 1 and 2 summed in O(1)
 
grid = ["..#", "...", "#.."]
R, C = len(grid), len(grid[0])
r, c = 0, 0
nbrs = [
    (r + dr, c + dc)
    for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1))
    if 0 <= r + dr < R and 0 <= c + dc < C
]
print(nbrs)  # [(1, 0), (0, 1)]: no wrap to row -1
 
 
def subsets(nums: list[int]) -> list[list[int]]:
    out: list[list[int]] = []
    path: list[int] = []
 
    def go(i: int) -> None:
        if i == len(nums):
            out.append(path[:])  # copy: path keeps changing
            return
        go(i + 1)  # skip nums[i]
        path.append(nums[i])  # take nums[i]
        go(i + 1)
        path.pop()  # undo
 
    go(0)
    return out
 
 
print(subsets([1, 2]))  # [[], [2], [1], [1, 2]]

Typing

AnnotationAcceptsUse for
list[T]lists onlyvalues you build, mutate or return
Sequence[T]list, tuple, str, range, ...read-only parameters that index or call len
Iterable[T]anything a for loop takesparameters you only loop over once
MutableSequence[T]list, deque, array, UserListparameters you edit in place without demanding a list
tuple[T, ...]tuples of any lengthimmutable results

typing.List is a deprecated alias; write list[int]. Import the abstract types from collections.abc.

from collections.abc import Iterable, Sequence
 
 
def mean(xs: Sequence[float]) -> float:  # read-only
    return sum(xs) / len(xs)
 
 
def add_all(dst: list[int], src: Iterable[int]) -> None:
    dst.extend(src)  # needs a real list
 
 
ints: list[int] = [1, 2]
mean(ints)  # OK: Sequence is covariant
nums: list[float] = ints  # ❌ type error: list is invariant
empty: list[str] = []  # annotate empty lists
maybe: list[int | None] = [1, None]
type Matrix = list[list[float]]  # alias (3.12+)
 
 
def first[T](xs: Sequence[T]) -> T | None:  # 3.12+
    return xs[0] if xs else None

list is invariant because it is writable: if list[int] were accepted as list[float], the callee could append 0.5 to your list of ints. Sequence has no mutating methods, so it is covariant and accepts list[int] where Sequence[float] is expected.

Coming from JavaScript

The TypeScript version of this sheet is Arrays.

JS ArrayPython list
xs.lengthlen(xs)
xs.push(x), xs.push(...ys)xs.append(x), xs.extend(ys)
xs.pop()xs.pop(): raises IndexError on empty instead of undefined
xs.shift(), xs.unshift(x)xs.pop(0), xs.insert(0, x): O(n), so a deque for queues
xs.splice(i, n)del xs[i : i + n], or slice it first to keep the removed items
xs.splice(i, 0, a, b)xs[i:i] = [a, b]
xs.slice(a, b), xs.at(-1)xs[a:b], xs[-1]
xs.concat(ys), [...xs, ...ys]xs + ys, [*xs, *ys]
xs.indexOf(x)xs.index(x): raises ValueError, no -1
xs.includes(x)x in xs
xs.find(p), xs.findIndex(p)next((x for x in xs if p(x)), None), next((i for i, x in enumerate(xs) if p(x)), -1)
xs.findLast(p)next((x for x in reversed(xs) if p(x)), None)
xs.map(f), xs.filter(p)[f(x) for x in xs], [x for x in xs if p(x)]
xs.flat(), xs.flatMap(f)list(chain.from_iterable(xs)), [y for x in xs for y in f(x)]
xs.reduce(f, init)functools.reduce(f, xs, init); usually sum, max, min
xs.some(p), xs.every(p)any(p(x) for x in xs), all(p(x) for x in xs)
xs.join(", ")", ".join(xs): items must be str, so ", ".join(map(str, xs))
xs.sort((a, b) => a - b)xs.sort(): numbers already sort numerically
xs.toSorted(cmp), xs.toReversed()sorted(xs, key=...), xs[::-1]
xs.with(i, v)ys = xs.copy() then ys[i] = v
xs.forEach((x, i) => ...)for i, x in enumerate(xs):
new Array(n).fill(0)[0] * n
Array.from({ length: n }, (_, i) => i)list(range(n))
Array.isArray(x)isinstance(x, list)
xs.length = 0xs.clear()
[...new Set(xs)]list(dict.fromkeys(xs)) (keeps first-seen order)
structuredClone(xs)copy.deepcopy(xs)

Recipes

Flatten, chunk, dedupe and group-by recipes are in Fundamentals.

Sliding window

When you need every run of k consecutive items: moving averages, n-grams. The variable-size, two-pointer window is in Sliding window.

from collections import deque
from collections.abc import Iterable, Iterator
from itertools import islice
 
 
def windows[T](
    items: Iterable[T], k: int
) -> Iterator[tuple[T, ...]]:
    it = iter(items)
    win = deque(islice(it, k), maxlen=k)
    if len(win) == k:
        yield tuple(win)
    for x in it:
        win.append(x)  # maxlen drops the oldest
        yield tuple(win)
 
 
prices = [10, 11, 13, 12, 15]
print([sum(w) / 3 for w in windows(prices, 3)])
# [11.333333333333334, 12.0, 13.333333333333334]

Partition by a predicate

When you want the matches and the rest in one pass.

from collections.abc import Callable, Iterable
 
 
def partition[T](
    items: Iterable[T], pred: Callable[[T], bool]
) -> tuple[list[T], list[T]]:
    yes: list[T] = []
    no: list[T] = []
    for x in items:
        (yes if pred(x) else no).append(x)
    return yes, no
 
 
evens, odds = partition(range(7), lambda n: n % 2 == 0)
print(evens, odds)  # [0, 2, 4, 6] [1, 3, 5]

Sort by several keys, mixed directions

When some keys run ascending and others descending, and not every key is a number you can negate.

from dataclasses import dataclass
from operator import attrgetter
 
 
@dataclass
class Player:
    team: str
    score: int
    name: str
 
 
players = [
    Player("red", 10, "cy"),
    Player("blue", 10, "ann"),
    Player("red", 12, "bo"),
]
# numeric keys can be negated: team, score desc, name
players.sort(key=lambda p: (p.team, -p.score, p.name))
 
# strings can't: sort by the minor key first, then the
# major one; stability keeps the minor order in ties
players.sort(key=attrgetter("name"), reverse=True)
players.sort(key=attrgetter("team"))
print([(p.team, p.name) for p in players])
# [('blue', 'ann'), ('red', 'cy'), ('red', 'bo')]

Natural sort

When file2 should come before file10.

import re
 
 
def natural_key(s: str) -> list[int | str]:
    parts = re.split(r"(\d+)", s.casefold())
    return [int(p) if p.isdecimal() else p for p in parts]
 
 
files = ["file10.txt", "File2.txt", "file1.txt"]
print(sorted(files, key=natural_key))
# ['file1.txt', 'File2.txt', 'file10.txt']

The split alternates text and digits, so items at the same position always have the same type and never compare int with str. isdecimal() matches exactly what \d captured; isdigit() would also accept "²", which int() rejects.

Keep a list sorted

When items arrive one at a time and you need them in order, with binary search.

import bisect
from dataclasses import dataclass
 
 
@dataclass
class Job:
    due: int
    name: str
 
 
queue: list[Job] = []
for job in [Job(5, "b"), Job(1, "a"), Job(9, "c")]:
    bisect.insort(queue, job, key=lambda j: j.due)
print([j.name for j in queue])  # ['a', 'b', 'c']
 
i = bisect.bisect_left(queue, 5, key=lambda j: j.due)
print(queue[i].name)  # b

insort applies key to the new item; bisect_left and bisect_right do not, so pass them the key value (5), not a Job.

Move, rotate and swap

When reordering after a drag and drop, or cycling through a list.

def move[T](xs: list[T], src: int, dst: int) -> None:
    xs.insert(dst, xs.pop(src))
 
 
def rotate[T](xs: list[T], k: int) -> list[T]:
    if not xs:
        return []
    k %= len(xs)  # also handles k < 0 and k > len
    return xs[-k:] + xs[:-k]
 
 
tasks = ["a", "b", "c", "d"]
move(tasks, 0, 2)
print(tasks)  # ['b', 'c', 'a', 'd']
print(rotate([1, 2, 3, 4, 5], 2))  # [4, 5, 1, 2, 3]
tasks[0], tasks[-1] = tasks[-1], tasks[0]  # swap
print(tasks)  # ['d', 'c', 'a', 'b']

collections.deque.rotate(k) does the same rotation in place.

Transpose and rotate a grid

When working with a matrix stored as a list of rows.

m = [[1, 2, 3], [4, 5, 6]]
print([list(col) for col in zip(*m)])
# [[1, 4], [2, 5], [3, 6]]
print([list(row) for row in zip(*m[::-1])])  # 90° right
# [[4, 1], [5, 2], [6, 3]]
print([list(row) for row in zip(*m)][::-1])  # 90° left
# [[3, 6], [2, 5], [1, 4]]

zip stops at the shortest row; ragged grids need itertools.zip_longest(*m, fillvalue=None).

Diff two lists

When syncing: what was added and what was removed, in order.

from collections import Counter
 
old = ["a", "b", "c", "d"]
new = ["b", "d", "e"]
old_set, new_set = set(old), set(new)
added = [x for x in new if x not in old_set]  # ['e']
removed = [x for x in old if x not in new_set]
print(added, removed)  # ['e'] ['a', 'c']
 
# with duplicates: multiset difference
extra = Counter([1, 1, 2, 3]) - Counter([1, 3])
print(sorted(extra.elements()))  # [1, 2]

References