DEV Community

Arthur
Arthur

Posted on

How to Find and Fix Memory Leaks in Python

Hello, I’m Arthur. Have you ever noticed a Python application using more and more RAM even though you're not doing anything unusual?

At first, everything works fine. After a few hours, the application becomes slower. Eventually, it may crash with an out-of-memory error.

Restarting the application might temporarily fix the problem, but it doesn't solve the underlying issue.

One possible cause is a memory leak.

What Causes Memory Leaks in Python?

Python automatically manages memory, so you don't normally need to free objects manually.

However, objects can remain in memory when your application still holds references to them.

Common examples include:

  • Storing every request in a list.
  • Keeping old results in an unlimited cache.
  • Accidentally retaining large objects.
  • Running background tasks that accumulate data.
  • Holding references to objects that are no longer needed.

Let's look at a simple example.

1. Understand the Problem

Imagine an application that saves every incoming request:

requests_log = []

def handle_request(data):
    requests_log.append(data)
Enter fullscreen mode Exit fullscreen mode

If the application receives thousands of requests, this list continues growing.

If each item contains a large amount of data, memory consumption can increase significantly.

A better approach might be to keep only the most recent entries:

from collections import deque

requests_log = deque(maxlen=1000)

def handle_request(data):
    requests_log.append(data)
Enter fullscreen mode Exit fullscreen mode

Now the collection retains at most 1,000 entries. Choose an appropriate limit for your application, and avoid storing sensitive request data unnecessarily.

2. Track Memory With tracemalloc

Python includes a built-in module called tracemalloc that helps identify where Python allocations are happening.

You don't need to install another package.

import tracemalloc

tracemalloc.start()

# Run the code you want to investigate here.
data = [bytearray(1024) for _ in range(10000)]

snapshot = tracemalloc.take_snapshot()

for stat in snapshot.statistics("lineno")[:10]:
    print(stat)
Enter fullscreen mode Exit fullscreen mode

This shows the source locations responsible for the largest tracked memory allocations.

For a real application, take snapshots at different times and compare them:

before = tracemalloc.take_snapshot()

# Run the workload you want to investigate.

after = tracemalloc.take_snapshot()

for stat in after.compare_to(before, "lineno")[:10]:
    print(stat)
Enter fullscreen mode Exit fullscreen mode

If the same part of your application keeps accumulating allocations, investigate that code first.

Remember that tracemalloc tracks Python memory allocations, not every possible source of process memory usage.

3. Check the Application's RAM Usage

You can also monitor the Python process with psutil.

Install it:

pip install psutil
Enter fullscreen mode Exit fullscreen mode

Then run:

import os
import psutil

process = psutil.Process(os.getpid())

memory = process.memory_info()

print(f"RSS memory: {memory.rss / 1024**2:.2f} MB")
Enter fullscreen mode Exit fullscreen mode

RSS represents the physical memory currently resident for that process.

Run this measurement at regular intervals while testing your application. If memory usage continually rises under the same workload, investigate further before assuming that you have found a leak.

4. Be Careful With Caches

Caching can improve performance, but an unlimited cache can create a memory problem.

For example:

cache = {}

def get_result(key, result):
    cache[key] = result
    return cache[key]
Enter fullscreen mode Exit fullscreen mode

If every request creates a new key, the dictionary may grow indefinitely.

One solution is to use a cache with a maximum size. For example, Python's functools.lru_cache supports a size limit:

from functools import lru_cache

@lru_cache(maxsize=500)
def calculate_result(number):
    return number * number
Enter fullscreen mode Exit fullscreen mode

This works well for suitable repeatable calculations. For applications where cached data changes frequently, consider expiration rules or an appropriate external cache.

5. Why This Matters on a VPS

Memory leaks are especially important in long-running services, APIs, bots, workers, and background jobs.

A process that gradually consumes more RAM can affect other applications running on the same server.

When choosing infrastructure for a Python application, consider memory requirements, CPU allocation, storage, and expected traffic. If you're comparing hosting options, HelloServer VPS hosting is one place to review alongside other providers.

But adding more RAM should not replace investigating a memory problem. More resources may delay the symptoms without fixing the cause.

Final Thoughts

When a Python application becomes slower over time, don't immediately assume the server is too small.

Measure memory usage, compare allocation snapshots, inspect growing collections, and test the application under a repeatable workload.

The most useful debugging process is the one that helps you find the cause rather than repeatedly restarting the application.

Top comments (0)