121. Which Python profiling tools would you use to investigate CPU, memory, and latency problems?
Explain when to use cProfile, pstats, timeit, py-spy, tracemalloc, memory profilers, application metrics, and distributed tracing, and how you would avoid drawing conclusions from unrealistic microbenchmarks.
I would start with production symptoms and metrics, not a profiler. First I would check p95 latency, error rate, CPU use, memory growth, queue delay, and dependency timing. Then I would choose the tool based on the suspected bottleneck. I would use cProfile and pstats for function level CPU work, py spy for low overhead sampling, tracemalloc and memory profilers for allocations, and tracing for service latency. I would use timeit only for small isolated checks, not as proof of production speed.
I would treat profiling as an evidence gathering process. The first step is to define the visible problem. For example, the problem may be high p95 latency, growing memory, high CPU use, or slow background jobs. Then I would set the measurement boundary. That means deciding whether I am measuring one function, one request, one worker job, or the whole service.
- What user-visible symptom and measurable performance target define success?
- What workload, environment, data size, and concurrency level should I assume?
- What profiling evidence is available, and which tradeoffs or system changes are allowed?
For latency, I would start with application metrics and distributed tracing. Application metrics show trends such as request rate, p95 latency, p99 latency, error rate, CPU use, and memory use. Distributed tracing follows one request across services, databases, queues, and network calls. This helps separate time spent doing Python work from time spent waiting.
For CPU problems, I would use cProfile in a controlled run. It is deterministic, which means it records function calls and time during the run. Then I would use pstats to sort the output by total time and cumulative time. Total time shows time inside one function. Cumulative time includes time spent in functions it calls. If I need to observe a running process with less overhead, I would use py spy. It is a sampling profiler, which means it checks stack frames at intervals instead of recording every call.
For memory problems, I would use tracemalloc first when Python allocations are suspected. It can compare snapshots and show where memory was allocated. If memory grows line by line in a script or worker, I would use a memory profiler. I would remember that tracemalloc does not see every native allocation from C extensions.
For tiny code experiments, I would use timeit. It is useful for checking a small function in isolation. I would not use it to prove that a web endpoint is faster. A microbenchmark may ignore database time, network delay, serialization, cache behavior, and concurrency.
After finding evidence, I would make one targeted change. Then I would retest with the same representative workload. I would compare before and after latency, CPU, memory, errors, and dependency time. I would also verify that results stay correct. The goal is to reduce the measured bottleneck without moving the problem somewhere else.
First, define the symptom. Use a concrete metric such as p95 latency, CPU use, memory growth, or queue delay. Second, capture a baseline from metrics, logs, and traces. Third, reproduce the problem with representative requests, data sizes, and dependency behavior. Fourth, classify the bottleneck using evidence. It may be CPU work, memory allocation, database time, network wait, queue delay, locks, or event loop blocking. Fifth, choose the tool that matches the suspected problem. Use cProfile and pstats for controlled CPU runs. Use py spy for sampling a running process. Use tracemalloc and memory profilers for memory growth. Use distributed tracing for end to end latency. Sixth, make one targeted change. Seventh, retest with the same workload and verify correctness.
The cost depends on the tool. Application metrics and traces are useful in production, but they can add small overhead and sampling bias. cProfile gives detailed CPU data, but it can slow the program during a controlled run. py spy has lower overhead, but it may miss very short events. tracemalloc helps with Python allocations, but it may miss memory held by native libraries. timeit is cheap for small code, but it does not include real service behavior. The main cost is running realistic tests and comparing results carefully.
Interviewers ask this to check whether you measure before optimizing. They want to see if you can pick the right tool for CPU, memory, and latency symptoms. They also want to know if you understand tool limits. A strong candidate does not trust one small benchmark. They compare real metrics, traces, and profiler evidence.
A common mistake is optimizing code before measuring the real symptom. Another mistake is using average latency and ignoring p95 or p99 latency. Some candidates use timeit on a tiny function and treat it as proof that the full service is faster. That is risky because production includes databases, network calls, queues, payload sizes, and concurrency. Another mistake is using cProfile once and assuming it explains memory or network delay. Some people confuse CPU time with waiting time. Others ignore memory growth, allocation churn, connection pool waits, lock contention, or event loop blocking. A final mistake is changing the workload between before and after tests.
Start with the symptom and metric. Then name the tool that answers that exact question. Explain one limitation for each tool. Say that timeit is useful for small isolated checks, but real performance needs representative load, metrics, traces, and before and after comparison.









