Picture by Creator
# Introduction
Python is without doubt one of the most beginner-friendly languages on the market. However in case you’ve labored with it for some time, you have in all probability run into loops that take minutes to complete, knowledge processing jobs that hog all of your reminiscence, and extra.
You need not develop into a efficiency optimization knowledgeable to make vital enhancements. Most sluggish Python code is due to a handful of frequent points which can be easy to repair as soon as you recognize what to search for.
On this article, you may study 5 sensible strategies to hurry up sluggish Python code, with before-and-after examples that present the distinction.
Yow will discover the code for this text on GitHub.
# Stipulations
Earlier than we get began, be sure to have:
- Python 3.10 or increased put in
- Familiarity with features, loops, and lists
- Some familiarity with the time module from the usual library
For a few examples, additionally, you will want the next libraries:
# 1. Measuring Earlier than Optimizing
Earlier than modifying a single line of code, you must know the place the slowness really is. Optimizing the fallacious a part of your code wastes time and might even make issues worse.
Python’s commonplace library features a easy solution to time any block of code: the time module. For extra detailed profiling, cProfile reveals you precisely which features are taking the longest.
As an instance you’ve gotten a script that processes a listing of gross sales data. Right here is learn how to discover the sluggish half:
import time
def load_records():
# Simulate loading 100,000 data
return record(vary(100_000))
def filter_records(data):
return [r for r in records if r % 2 == 0]
def generate_report(data):
return sum(data)
# Time every step
begin = time.perf_counter()
data = load_records()
print(f"Load : {time.perf_counter() - start:.4f}s")
begin = time.perf_counter()
filtered = filter_records(data)
print(f"Filter : {time.perf_counter() - start:.4f}s")
begin = time.perf_counter()
report = generate_report(filtered)
print(f"Report : {time.perf_counter() - start:.4f}s")
Output:
Load : 0.0034s
Filter : 0.0060s
Report : 0.0012s
Now you recognize the place to focus. filter_records() is the slowest step, adopted by load_records(). In order that’s the place any optimization effort will repay. With out measuring, you may need frolicked optimizing generate_report(), which was already quick.
The time.perf_counter() perform is extra exact than time.time() for brief measurements. Use it each time you might be timing code efficiency.
Rule of thumb: by no means guess the place the bottleneck is. Measure first, then optimize.
# 2. Utilizing Constructed-in Features and Customary Library Instruments
Python’s built-in features — sum(), map(), filter(), sorted(), min(), max() — are applied in C underneath the hood. They’re considerably sooner than writing equal logic in pure Python loops.
Let’s evaluate manually summing a listing versus utilizing the built-in:
import time
numbers = record(vary(1_000_000))
# Guide loop
begin = time.perf_counter()
complete = 0
for n in numbers:
complete += n
print(f"Manual loop : {time.perf_counter() - start:.4f}s → {total}")
# Constructed-in sum()
begin = time.perf_counter()
complete = sum(numbers)
print(f"Built-in : {time.perf_counter() - start:.4f}s → {total}")
Output:
Guide loop : 0.1177s → 499999500000
Constructed-in : 0.0103s → 499999500000
As you possibly can see, utilizing built-in features is almost 6x sooner.
The identical precept applies to sorting. If you must type a listing of dictionaries by a key, Python’s sorted() with a key argument is each sooner and cleaner than sorting manually. Right here is one other instance:
orders = [
{"id": "ORD-003", "amount": 250.0},
{"id": "ORD-001", "amount": 89.99},
{"id": "ORD-002", "amount": 430.0},
]
# Sluggish: handbook comparability logic
def manual_sort(orders):
for i in vary(len(orders)):
for j in vary(i + 1, len(orders)):
if orders[i]["amount"] > orders[j]["amount"]:
orders[i], orders[j] = orders[j], orders[i]
return orders
# Quick: built-in sorted()
sorted_orders = sorted(orders, key=lambda o: o["amount"])
print(sorted_orders)
Output:
[{'id': 'ORD-001', 'amount': 89.99}, {'id': 'ORD-003', 'amount': 250.0}, {'id': 'ORD-002', 'amount': 430.0}]
As an train, attempt to time the above approaches.
Rule of thumb: earlier than writing a loop to do one thing frequent — summing, sorting, discovering the max — verify if Python already has a built-in for it. It virtually at all times does, and it’s virtually at all times sooner.
# 3. Avoiding Repeated Work Inside Loops
One of the frequent efficiency errors is doing costly work inside a loop that could possibly be achieved as soon as exterior it. Each iteration pays the associated fee, even when the end result by no means adjustments.
Right here is an instance: validating a listing of product codes towards an authorised record.
import time
authorised = ["SKU-001", "SKU-002", "SKU-003", "SKU-004", "SKU-005"] * 1000
incoming = [f"SKU-{str(i).zfill(3)}" for i in range(5000)]
# Sluggish: len() and record membership verify on each iteration
begin = time.perf_counter()
legitimate = []
for code in incoming:
if code in authorised: # record search is O(n) — sluggish
legitimate.append(code)
print(f"List check : {time.perf_counter() - start:.4f}s → {len(valid)} valid")
# Quick: convert authorised to a set as soon as, earlier than the loop
begin = time.perf_counter()
approved_set = set(authorised) # set lookup is O(1) — quick
legitimate = []
for code in incoming:
if code in approved_set:
legitimate.append(code)
print(f"Set check : {time.perf_counter() - start:.4f}s → {len(valid)} valid")
Output:
Listing verify : 0.3769s → 5 legitimate
Set verify : 0.0014s → 5 legitimate
The second strategy is far sooner, and the repair was simply shifting one conversion exterior the loop.
The identical sample applies to something costly that doesn’t change between iterations, like studying a config file, compiling a regex sample, or opening a database connection. Do it as soon as earlier than the loop, not as soon as per iteration.
import re
# Sluggish: recompiles the sample on each name
def extract_slow(textual content):
return re.findall(r'd+', textual content)
# Quick: compile as soon as, reuse
DIGIT_PATTERN = re.compile(r'd+')
def extract_fast(textual content):
return DIGIT_PATTERN.findall(textual content)
Rule of thumb: if a line inside your loop produces the identical end result each iteration, transfer it exterior.
# 4. Selecting the Proper Knowledge Construction
Python offers you many built-in knowledge buildings — lists, units, dictionaries, tuples — and selecting the fallacious one for the job could make your code a lot slower than it must be.
Crucial distinction is between lists and units for membership checks utilizing the in operator:
- Checking whether or not an merchandise exists in a listing takes longer because the record grows, as you need to scan via it one after the other
- A set makes use of hashing to reply the identical query in fixed time, no matter dimension
Let us take a look at an instance: discovering which buyer IDs from a big dataset have already positioned an order.
import time
import random
all_customers = [f"CUST-{i}" for i in range(100_000)]
ordered = [f"CUST-{i}" for i in random.sample(range(100_000), 10_000)]
# Sluggish: ordered is a listing
begin = time.perf_counter()
repeat_customers = [c for c in all_customers if c in ordered]
print(f"List : {time.perf_counter() - start:.4f}s → {len(repeat_customers)} found")
# Quick: ordered is a set
ordered_set = set(ordered)
begin = time.perf_counter()
repeat_customers = [c for c in all_customers if c in ordered_set]
print(f"Set : {time.perf_counter() - start:.4f}s → {len(repeat_customers)} found")
Output:
Listing : 16.7478s → 10000 discovered
Set : 0.0095s → 10000 discovered
The identical logic applies to dictionaries once you want quick key lookups, and to the collections module’s deque when you’re regularly including or eradicating gadgets from each ends of a sequence — one thing lists are sluggish at.
Here’s a fast reference for when to achieve for which construction:
| Want | Knowledge Construction to Use |
|---|---|
| Ordered sequence, index entry | record |
| Quick membership checks | set |
| Key-value lookups | dict |
| Counting occurrences | collections.Counter |
| Queue or deque operations | collections.deque |
Rule of thumb: if you’re checking if x in one thing inside a loop and one thing has various hundred gadgets, it ought to in all probability be a set.
# 5. Vectorizing Operations on Numeric Knowledge
In case your code processes numbers — calculations throughout rows of information, statistical operations, transformations — writing Python loops is virtually at all times the slowest potential strategy. Libraries like NumPy and pandas are constructed for precisely this: making use of operations to whole arrays directly, in optimized C code, with no Python loop in sight.
That is referred to as vectorization. As a substitute of telling Python to course of every factor one after the other, you hand the entire array to a perform that handles every little thing internally at C pace.
import time
import numpy as np
import pandas as pd
costs = [round(10 + i * 0.05, 2) for i in range(500_000)]
discount_rate = 0.15
# Sluggish: Python loop
begin = time.perf_counter()
discounted = []
for worth in costs:
discounted.append(spherical(worth * (1 - discount_rate), 2))
print(f"Python loop : {time.perf_counter() - start:.4f}s")
# Quick: NumPy vectorization
prices_array = np.array(costs)
begin = time.perf_counter()
discounted = np.spherical(prices_array * (1 - discount_rate), 2)
print(f"NumPy : {time.perf_counter() - start:.4f}s")
# Quick: pandas vectorization
prices_series = pd.Sequence(costs)
begin = time.perf_counter()
discounted = (prices_series * (1 - discount_rate)).spherical(2)
print(f"Pandas : {time.perf_counter() - start:.4f}s")
Output:
Python loop : 1.0025s
NumPy : 0.0122s
Pandas : 0.0032s
NumPy is almost 100x sooner for this operation. The code can be shorter and cleaner. No loop, no append(), only a single expression.
In case you are already working with a pandas DataFrame, the identical precept applies to column operations. At all times choose column-level operations over looping via rows with iterrows():
df = pd.DataFrame({"price": costs})
# Sluggish: row-by-row with iterrows
begin = time.perf_counter()
for idx, row in df.iterrows():
df.at[idx, "discounted"] = spherical(row["price"] * 0.85, 2)
print(f"iterrows : {time.perf_counter() - start:.4f}s")
# Quick: vectorized column operation
begin = time.perf_counter()
df["discounted"] = (df["price"] * 0.85).spherical(2)
print(f"Vectorized : {time.perf_counter() - start:.4f}s")
Output:
iterrows : 34.5615s
Vectorized : 0.0051s
The iterrows() perform is without doubt one of the most typical efficiency traps in pandas. When you see it in your code and you might be engaged on various thousand rows, changing it with a column operation is sort of at all times value doing.
Rule of thumb: if you’re looping over numbers or DataFrame rows, ask whether or not NumPy or pandas can do the identical factor as a vectorized operation.
# Conclusion
Sluggish Python code is often a sample downside. Measuring earlier than optimizing, leaning on built-ins, avoiding repeated work in loops, selecting the correct knowledge construction, and utilizing vectorization for numeric work will cowl the overwhelming majority of efficiency points you’ll run into as a newbie.
Begin with tip one each time: measure. Discover the precise bottleneck, repair that, and measure once more. You can be shocked how a lot headroom there’s earlier than you want something extra superior.
The 5 strategies on this article cowl the most typical causes of sluggish Python code. However typically you must go additional:
- Multiprocessing — in case your job is CPU-bound and you’ve got a multi-core machine, Python’s
multiprocessingmodule can break up the work throughout cores - Async I/O — in case your code spends most of its time ready on community requests or file reads,
asynciocan deal with many duties concurrently - Dask or Polars — for datasets too giant to slot in reminiscence, these libraries scale past what pandas can deal with
These are value exploring after you have utilized the fundamentals and nonetheless want extra headroom. Completely satisfied coding!
Bala Priya C is a developer and technical author from India. She likes working on the intersection of math, programming, knowledge science, and content material creation. Her areas of curiosity and experience embrace DevOps, knowledge science, and pure language processing. She enjoys studying, writing, coding, and occasional! At the moment, she’s engaged on studying and sharing her data with the developer group by authoring tutorials, how-to guides, opinion items, and extra. Bala additionally creates partaking useful resource overviews and coding tutorials.



