Python
memory usage
object size
sys getsizeof
programming

Find out how much memory is being used by an object in Python

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

“How much memory does this object use?” sounds simple in Python, but there are two different answers. You can measure the size of the object container itself, or you can measure the total memory retained by that object and everything it references. Good memory analysis starts by choosing the right one.

Shallow Size With sys.getsizeof

The standard library function sys.getsizeof reports the shallow size of a single object in bytes. That includes the object header and immediate storage managed by that object, but not the memory used by referenced child objects.

python
1import sys
2
3numbers = [1, 2, 3, 4, 5]
4print(sys.getsizeof(numbers))
5print(sys.getsizeof(42))
6print(sys.getsizeof("hello"))

This is useful when you want to compare the overhead of different container types or understand how object headers affect memory. It is also fast and available everywhere.

The limitation is important. For a list, sys.getsizeof tells you how much space the list structure uses for its slots, not the full cost of all the integers stored inside. That means two lists with the same length may report the same shallow size even if their elements point to very different objects.

Measuring Retained Memory Recursively

If you need a better approximation of total memory usage, you have to walk the object graph and add up the sizes of referenced objects. A common approach is a recursive helper that tracks visited object ids so shared references are only counted once.

python
1import sys
2
3def deep_getsizeof(obj, seen=None):
4    if seen is None:
5        seen = set()
6
7    obj_id = id(obj)
8    if obj_id in seen:
9        return 0
10    seen.add(obj_id)
11
12    size = sys.getsizeof(obj)
13
14    if isinstance(obj, dict):
15        size += sum(deep_getsizeof(k, seen) + deep_getsizeof(v, seen) for k, v in obj.items())
16    elif isinstance(obj, (list, tuple, set, frozenset)):
17        size += sum(deep_getsizeof(item, seen) for item in obj)
18
19    return size
20
21payload = {
22    "name": "report",
23    "values": list(range(1000)),
24    "flags": {"debug": True, "dry_run": False},
25}
26
27print(deep_getsizeof(payload))

This kind of helper is good enough for many scripts and debugging sessions. The seen set prevents double counting when several objects reference the same list or dictionary. Without that guard, recursive totals become misleading very quickly.

A recursive helper is still an approximation. Some Python objects store data in C extensions or in custom allocators that a generic walker cannot fully understand. For example, a NumPy array has a Python object wrapper plus a large native buffer that deserves separate treatment.

Profiling Real Allocations With tracemalloc

Sometimes you do not want the size of one object at all. You want to know what code path allocated memory and how much additional memory appeared during an operation. That is where tracemalloc is a better tool.

python
1import tracemalloc
2
3def build_data():
4    return [{"id": i, "name": f"user-{i}"} for i in range(10_000)]
5
6tracemalloc.start()
7before = tracemalloc.take_snapshot()
8
9data = build_data()
10
11after = tracemalloc.take_snapshot()
12stats = after.compare_to(before, "lineno")
13
14for stat in stats[:5]:
15    print(stat)

This does not answer “what is the exact size of data?” in the same way as sys.getsizeof. Instead, it answers “what new allocations happened while I built data?” That distinction matters when you are tracking leaks or unexpected growth in a service.

Third-Party Tools for Deep Inspection

If you need more complete object sizing, libraries such as pympler can be more accurate than a hand-written recursive function because they know about more container types and edge cases.

python
1from pympler import asizeof
2
3payload = {"items": [list(range(100)) for _ in range(20)]}
4print(asizeof.asizeof(payload))

This is often the most practical choice when you are doing serious diagnostics in a local environment. You still need to interpret the number carefully, but you avoid rewriting object walkers for every project.

Common Pitfalls

The biggest pitfall is assuming sys.getsizeof gives the full size of nested containers. It does not. For lists, dictionaries, and custom objects, it usually reports only the top-level object.

Another frequent mistake is double counting shared references. If two keys point to the same list, a naive recursive walk may count that list twice. Any deep-sizing helper should track visited object ids.

CPython implementation details also matter. Memory usage can differ across Python versions and interpreter builds, so byte counts are good for diagnostics but not for exact cross-platform guarantees.

Finally, object size and process memory are not the same thing. Python allocators may keep freed memory available for reuse, so your object can disappear while the process size stays almost unchanged.

Summary

  • Use sys.getsizeof for shallow object size.
  • Use a recursive walk or a library such as pympler for a deeper estimate.
  • Use tracemalloc when you need allocation history rather than a single object size.
  • Avoid double counting by tracking visited objects.
  • Treat the reported byte count as a useful diagnostic, not as a universal absolute truth.

Course illustration
Course illustration

All Rights Reserved.