How do I profile memory usage in Python?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Memory profiling in Python is less about one magic tool and more about combining views of your program over time. A script might have high peak memory, a slow leak, or temporary spikes during object creation. If you only check one metric, you may optimize the wrong part. The practical approach is to start with lightweight built-in tools, then move to line-level profiling when you identify suspicious code paths. You should also separate Python-object growth from native-extension memory usage, because NumPy, TensorFlow, and other libraries may allocate outside the Python allocator. This article outlines a repeatable workflow to find, explain, and fix memory issues with minimal guesswork.
Core Sections
1. Start with tracemalloc for allocation hotspots
tracemalloc tracks Python memory allocations and can compare snapshots before and after a workload.
For leak hunting, take two snapshots and compare them:
This quickly identifies files and lines responsible for net growth.
2. Use process-level monitoring for RSS trends
If memory rises even when tracemalloc looks stable, native allocations may be involved. Monitor resident memory size (RSS) from the OS.
Pair this with workload phases so you can correlate spikes with steps like parsing, model inference, or serialization.
3. Line-by-line profiling with memory_profiler
When one function is suspicious, instrument it directly.
Run with:
You get per-line increments, making it obvious whether growth comes from list accumulation, copies, or temporary objects.
4. Fix patterns, then re-measure
Common wins include streaming instead of loading full datasets, replacing large intermediate lists with generators, deleting references sooner, and reusing buffers. For long-running services, verify memory behavior over hours, not seconds. A profile that looks stable for one request may leak across thousands.
A robust loop is: baseline, isolate hotspot, apply one change, benchmark again. Multi-change commits make it hard to prove which fix worked.
5. CI-friendly checks
For critical pipelines, add a regression test that enforces peak-memory budget for representative input sizes. You can measure max RSS in integration tests and fail when growth exceeds expected tolerance. That prevents subtle leaks from shipping unnoticed.
Validation and production readiness
A reliable implementation should include more than a working snippet. Add a small reproducible dataset or input fixture that exercises expected behavior and edge cases, then codify it in automated tests. Include at least one “happy path,” one malformed input case, and one boundary condition so regressions are caught early. Instrument key steps with structured logs or metrics to make failures diagnosable in runtime environments, not just local development. If performance is relevant, keep a lightweight benchmark that can be rerun after refactors to ensure behavior stays within budget.
Operationally, document assumptions near the code: required library versions, environment variables, timezone/locale expectations, and failure handling strategy. For team workflows, add one integration test that mirrors real usage rather than only unit-level checks. This reduces drift between example code and production behavior. Treat these checks as part of feature completion, because most long-term issues are caused by unvalidated assumptions rather than syntax errors.
Common Pitfalls
- Relying only on
sys.getsizeof()and missing nested or referenced object memory. - Assuming all memory appears in
tracemallocwhen native libraries allocate outside Python. - Profiling toy inputs that do not reproduce production object lifetimes.
- Interpreting one-time startup allocations as leaks without steady-state comparison.
- Applying multiple optimizations at once and losing causal evidence of improvement.
Summary
Effective Python memory profiling is a layered process: use tracemalloc for Python allocation hotspots, RSS monitoring for total process memory, and line-level tools for precise function diagnosis. Treat profiling as an experiment, not a one-shot command. Capture baselines, make focused changes, and validate under realistic workloads. With that workflow, you can distinguish true leaks from normal growth and ship fixes that measurably improve stability and performance.
Related reading
- How do I query between two dates using MySQL?
- How do I replace weak references when using ARC and targeting iOS 4.0?
- How do I resize the UIImage to reduce upload image size
- How do I scale one rectangle to the maximum size possible within another rectangle?
- How do I properly assert that an exception gets raised in pytest?
- How do I properly assert that an exception gets raised in pytest?
- How do I search for a number in a 2d array sorted left to right and top to bottom?
- How do I select an entire row which has the largest ID in the table?

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.