Profile Python with Flame Graphs

Use Python's sampling profiler to find and fix a slow line of code.

Introduction

30 Second Summary

A script can feel slow long before you know which line is responsible. A timer measures the delay, but it cannot reveal where the program spends its time.

In this project, you will use Python 3.15's sampling profiler to find the bottleneck in a deliberately slow script. Before-and-after flame graphs prove the speedup after your fix.

What You'll Build

Your finished flame graph opens in a browser with one wide bar pointing straight to the slow function.

By the end of this project, you'll have:

  • A measured baseline runtime that shows exactly how long your slow script takes.
  • A pair of browser-ready visual reports showing function-level width plus a line-level heatmap of the hottest source lines.
  • Clear before-and-after evidence showing a narrower hotspot plus a lower runtime after your fix.
  • Secret Mission: An optional challenge to push your profiling skills further.

Are there any prerequisites?

You need uv installed before you begin. You also need a terminal plus a browser.

Before We Start

The profiler in this project belongs to the Python 3.15 standard library. An older interpreter cannot run the profiling commands you will use later.

This step gives you a terminal workspace with uv managing the required Python release. You will also confirm that a web browser is ready for the HTML reports.

In this step, get ready to:
  • Create a terminal workspace for the project.
  • Install uv for managing Python versions.
  • Install Python 3.15 for the project.
Create your project workspace

A dedicated folder keeps the script and its profiling reports together. It also gives uv one location for the project-specific Python version pin.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open system search.
  • Type Terminal on macOS or PowerShell on Windows into the search field.
  • Press Enter to open the terminal application.

macOS

  • Create the python-flame-graph folder on your Desktop by running these commands:
cd ~/Desktop
mkdir python-flame-graph
cd python-flame-graph
pwd

What do these commands do?

  • The first command moves your terminal to the Desktop.
  • The second command creates the python-flame-graph folder.
  • The final commands enter the folder and print its location.

You should see a path ending with Desktop/python-flame-graph.

Folder Already Exists?

Skip the folder creation command if your terminal says that the folder already exists. Run the final two commands to enter it and print its location.

If the printed path points somewhere else, help me reach my python-flame-graph folder in the terminal.

Windows

  • Create the python-flame-graph folder on your Desktop by running these commands:
cd ~/Desktop
mkdir python-flame-graph
cd python-flame-graph
pwd

What do these commands do?

  • The first command moves PowerShell to the Desktop.
  • The second command creates the python-flame-graph folder.
  • The final commands enter the folder and print its location.

You should see a path ending with Desktop\python-flame-graph.

Folder Already Exists?

Skip the folder creation command if PowerShell says that the folder already exists. Run the final two commands to enter it and print its location.

If the printed path points somewhere else, help me reach my python-flame-graph folder in PowerShell.

Install uv

Uv manages Python installations without changing the interpreter used by unrelated projects. Its help output gives you a quick way to check whether the command is already available.

  • Check whether uv is available by running this command:
uv

What Does This Command Do?

The command starts uv without a subcommand. Its help menu confirms that your terminal can find the installed tool.

✔️ I see the uv help menu

Your uv installation is ready. Continue to the Python version check.

ⓧ I see command not found

The missing command means uv needs to be installed. Choose your operating system below to use the official standalone installer.

macOS

  • Install uv by running the official installer command:
curl -LsSf https://astral.sh/uv/install.sh | sh
What Does This Command Do?

The command downloads the official uv installer. Your shell runs the downloaded script to place uv in your user executable directory.

  • Close the current terminal after the installer finishes.
  • Start a fresh terminal through system search.
  • Return to the python-flame-graph folder by running this command:
cd ~/Desktop/python-flame-graph
Why Return to This Folder?

The Python version pin belongs inside python-flame-graph. Running the next commands here keeps that setting local to this project.

  • Confirm that the fresh terminal can find uv by running this command:
uv
What Should You See?

You should see the uv help menu. This confirms that the fresh terminal loaded the updated command path.

Still Cannot Run uv?

Confirm that you started a fresh terminal after the installer completed. A new terminal reloads the command path changed by the installer.

If the command is still missing, help me make uv available in my terminal.

Windows

  • Install uv by running the official installer command in PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
What Does This Command Do?

The command downloads the official uv installer through PowerShell. The temporary execution policy setting allows this installer script to run.

  • Close the current PowerShell window after the installer finishes.
  • Start a fresh PowerShell window through system search.
  • Return to the python-flame-graph folder by running this command:
cd ~/Desktop/python-flame-graph
Why Return to This Folder?

The Python version pin belongs inside python-flame-graph. Running the next commands here keeps that setting local to this project.

  • Confirm that the fresh PowerShell window can find uv by running this command:
uv
What Should You See?

You should see the uv help menu. This confirms that the fresh PowerShell window loaded the updated command path.

Still Cannot Run uv?

Confirm that you started a fresh PowerShell window after the installer completed. A new window reloads the command path changed by the installer.

If the command is still missing, help me make uv available in PowerShell.

Install Python 3.15 and verify your tools

This project needs Python 3.15 because that release contains the sampling profiler. Uv stores the project selection in a .python-version file.

  • Check which Python version uv currently selects by running this command:
uv run python --version

What Does This Command Check?

Uv runs the Python interpreter selected for the current folder. The printed version shows whether that interpreter meets the project requirement.

✔️ I see version 3.15 or higher

The required interpreter is already available through uv. Pinning 3.15 keeps every later project command on the intended release.

  • Pin this project folder to Python 3.15 by running this command:
uv python pin 3.15

What Does This Command Do?

Uv creates a .python-version file in the current folder. That file tells future uv commands to select Python 3.15 for this project.

You should see confirmation that uv pinned the project to Python 3.15.

Pin Created in the Wrong Folder?

Check that your terminal path ends with python-flame-graph. Return to that folder before running the pin command again.

If you cannot locate the pin, help me pin Python 3.15 inside my project folder.

ⓧ I see an older version

The older interpreter cannot provide the profiler used by this project. Uv can install the required release without replacing your existing interpreter.

  • Install Python 3.15 for this project by running these commands:
uv python install 3.15
uv python pin 3.15

What Do These Commands Do?

  • The first command installs the latest available Python 3.15 release managed by uv.
  • The second command records Python 3.15 as the version for this project folder.

The download can take a minute while uv retrieves the interpreter. You should see installation output followed by confirmation that the project version was pinned.

Python Download Failed?

Check that your internet connection permits the download shown in the terminal. Run the installation command again after restoring the connection.

If the download still fails, help me troubleshoot my uv Python installation.

ⓧ Command not found

Uv cannot currently start a Python interpreter from this folder. Installing the required release gives the project a known runtime.

  • Install Python 3.15 for this project by running these commands:
uv python install 3.15
uv python pin 3.15

What Do These Commands Do?

  • The first command downloads a Python 3.15 interpreter managed by uv.
  • The second command creates the project version pin in the current folder.

The download can take a minute while uv retrieves the interpreter. You should see installation output followed by confirmation that the project version was pinned.

Still Cannot Install Python?

Confirm that the uv help command worked earlier. Uv must be available before it can download Python.

If the installation still fails, help me install Python 3.15 through uv.

Before you run the final check, which Python version do you expect uv to select from this folder?

  • Verify the pinned interpreter by running this command:
uv run python --version

What Should You See?

The version string should begin with Python and include 3.15. This proves that Python 3.15 is installed and available through uv for the project.

That is the environment ready. Your profiling commands now have access to the Python release they require.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open system search.
  • Type the name of your preferred web browser into the search field.
  • Press Enter to open the browser.

You should see a browser window with a tab ready to open the local HTML reports you create later.

Your terminal is ready with Python 3.15 selected through uv. Next up, you will write the slow script that gives the profiler something useful to measure.

Create and Time a Slow Script

Your Python 3.15 runtime is ready through uv. The next goal is to give it a measurable bottleneck.

A sampling profiler needs enough runtime to collect useful samples. A tiny program can finish before its slowest work becomes visually clear.

In this step, get ready to:
  • Generate a large plain-text workload for the word counter.
  • Build a deliberately inefficient word-counting function.
  • Run the script normally to capture its baseline time.
Generate a profiling workload

A reliable profile needs enough repeated work to collect a useful sample. The generated file gives every word a unique name.

Repeated full-list counting then becomes expensive. That expense creates the hotspot you will investigate.

  • Create input.txt in the project folder by running this command in the terminal from earlier:
uv run python -c 'from pathlib import Path; path = Path("input.txt"); path.write_text(" ".join(f"word{i}" for i in range(20000)), encoding="utf-8"); print(f"Created {path} with {path.stat().st_size:,} bytes")'

What does this command do?

  • The uv run python -c command runs a short Python program with the project-selected interpreter.
  • The range(20000) expression produces 20,000 numbered words.
  • The Path.write_text() call stores those words in input.txt.
  • Confirm the terminal reports Created input.txt with 188,889 bytes.

Your profiling workload is ready. Its size gives the slow counter enough work to produce a useful runtime.

Input file not created?

Confirm the terminal is still using the project where Python 3.15 was selected. A different folder can place input.txt somewhere unexpected.

If the command still fails, help me create input.txt with uv and Python.

Build the slow counting function

The bottleneck comes from counting each word with another scan of the complete list. The same list gets searched 20,000 times.

  • Create slow_words.py beside input.txt in your code editor by pasting the code below into a new file:
from pathlib import Path
from time import perf_counter

# Keep the input location stable for normal and profiled runs.
INPUT_PATH = Path("input.txt")

def count_words_slow(words):
    """Count words by scanning the full list for every item."""
    counts = {}
    # This repeated scan creates the deliberate bottleneck.
    for word in words:
        counts[word] = words.count(word)
    return counts

What does this code do?

  • The Path import gives the script a direct reference to input.txt.
  • The perf_counter() import provides the clock used for the baseline measurement.
  • The count_words_slow() function stores each word count in a dictionary.
  • The words.count(word) call scans the full list during every loop iteration.
  • Save slow_words.py.
  • Test the counting function by running this command:
uv run python -c "from slow_words import count_words_slow; print(count_words_slow(['red', 'blue', 'red']))"

What does this test prove?

The -c option runs the short test directly through Python. Importing count_words_slow() confirms that the saved file can be loaded.

The repeated red value checks that duplicate words receive the correct count.

You should see {'red': 2, 'blue': 1} in the terminal. That result proves the deliberately slow function still counts correctly.

Function test failed?

Check that slow_words.py is saved beside input.txt. Confirm that the function name is exactly count_words_slow.

For help with an import or syntax problem, help me debug the slow word-counting function.

Capture the baseline time

The counting function works on a tiny example. The main program now loads the full workload before measuring the deliberate bottleneck.

  • In slow_words.py, paste the code below after the line return counts:

def main():
    # Load the generated workload from disk.
    text = INPUT_PATH.read_text(encoding="utf-8")
    words = text.split()

    # Time only the counting work that will be optimized later.
    started = perf_counter()
    counts = count_words_slow(words)
    elapsed = perf_counter() - started

    # Print a stable result plus the baseline measurement.
    print(f"Counted {len(counts):,} unique words.")
    print(f"Elapsed: {elapsed:.3f} seconds")

if __name__ == "__main__":
    main()

How is the baseline measured?

  • The read_text() call loads the generated workload from input.txt.
  • The first perf_counter() call captures the start time immediately before the slow work.
  • The second perf_counter() call captures the elapsed time after counting finishes.
  • The two print() calls expose the result size plus the baseline time.
  • Save slow_words.py.

This first deliberate run can sit quietly for several seconds. The repeated scans are consuming that time.

Before you run it, what elapsed result do you expect from repeatedly scanning the same word list?

  • Run the complete script with the project-selected Python interpreter by running:
uv run slow_words.py

What does this command do?

The uv run command executes slow_words.py inside the project environment. That keeps this normal run on the selected Python 3.15 interpreter.

You should see Counted 20,000 unique words. in the terminal. You should also see an Elapsed: line with a positive number of seconds.

  • Copy the number after Elapsed: here: your baseline time in seconds.

Good work. Your terminal now holds the before measurement that the optimized script must beat.

Baseline missing or too fast?

If the input file cannot be found, confirm that input.txt sits beside slow_words.py. If the run finishes almost immediately, confirm that the file contains all 20,000 unique words.

For help checking the workload or timing output, help me debug my missing baseline time.

✔️ Awesome, I've got everything!

Great. Double-check that slow_words.py is saved before moving on.

Keep input.txt beside the script. Your terminal should still show the baseline elapsed time.

ⓧ I'd like to double check the full code

Compare your complete slow_words.py file with this version. Keep the generated input.txt file in the same folder.

from pathlib import Path
from time import perf_counter

# Keep the input location stable for normal and profiled runs.
INPUT_PATH = Path("input.txt")

def count_words_slow(words):
    """Count words by scanning the full list for every item."""
    counts = {}
    # This repeated scan creates the deliberate bottleneck.
    for word in words:
        counts[word] = words.count(word)
    return counts

def main():
    # Load the generated workload from disk.
    text = INPUT_PATH.read_text(encoding="utf-8")
    words = text.split()

    # Time only the counting work that will be optimized later.
    started = perf_counter()
    counts = count_words_slow(words)
    elapsed = perf_counter() - started

    # Print a stable result plus the baseline measurement.
    print(f"Counted {len(counts):,} unique words.")
    print(f"Elapsed: {elapsed:.3f} seconds")

if __name__ == "__main__":
    main()

Your measurable bottleneck is ready. Next, you will capture it with the sampling profiler.

Generate the First Flame Graph

Your baseline timing proves that slow_words.py is slow. It cannot show which function consumes the time.

Now you will use Python 3.15's sampling profiler to capture snapshots while the workload runs. You will turn those samples into an interactive HTML flame graph.

In this step, get ready to:
  • Capture the slow workload as a reusable binary profile.
  • Replay the recording as a standalone HTML flame graph.
  • Identify the slow word-counting function by the width of its bar.
Capture reusable profiling evidence

A binary recording keeps the raw stack samples from one profiling run. The run command launches the script under the profiler from startup.

The profiler runs the same slow workload. Expect this capture to take about as long as your baseline run.

  • Switch back to the terminal from earlier.
  • Run the slow workload under the profiler with this command:
python -m profiling.sampling run --binary -o before-profile.recording slow_words.py

What does this command do?

  • The python -m profiling.sampling portion starts the standard-library profiler.
  • The run subcommand launches slow_words.py under the profiler.
  • The --binary option preserves the raw samples for later replay.
  • The -o option writes those samples to before-profile.recording.

You should see slow_words.py print its elapsed runtime before the terminal prompt returns. The completed command leaves before-profile.recording beside your script.

Recording not created?

  • Return to the folder that contains slow_words.py.
  • Confirm that input.txt is in the same folder.
  • Check that the command uses --binary before the output filename.

Ask for help with the failed capture by selecting help me troubleshoot the sampling-profiler recording.

That capture gives you a frozen before snapshot of the bottleneck. You can now create more than one visualization without running the slow workload again.

Replay the recording as a flame graph

Replay converts the saved samples into a browser visualization. The resulting file embeds the graph data and source context.

  • Generate the before flame graph from the saved recording by running this command:
python -m profiling.sampling replay --flamegraph --browser -o flamegraph-before.html before-profile.recording

How does replay work?

  • The replay subcommand reads the samples from before-profile.recording.
  • The --flamegraph option renders the sampled call stacks as nested rectangles.
  • The --browser option opens the generated report in your default browser.
  • The -o option saves the standalone report as flamegraph-before.html.

The command creates flamegraph-before.html in the same folder as the recording. Your browser should open the report in a new tab.

Browser tab did not open?

  • Confirm that the replay command completed without reporting a missing recording.
  • Select flamegraph-before.html through your browser's local-file picker.
  • Reload the local file if the browser shows an older report.

Ask for help opening the report by selecting help me open my generated flame graph.

Find the widest relevant bar

How do I read a flame graph?

Each rectangle represents a function on a sampled call stack. A wider rectangle means that function appeared in more samples.

The vertical layers show how functions called one another. Focus on the widest bar connected to your project code.

Before you inspect the graph, which function do you expect to occupy the most horizontal space?

  • Scan the rectangles associated with slow_words.py.
  • Hover over the widest relevant rectangle.
  • Confirm that its details point to the deliberately slow word-counting function.
  • Check that the report shows source context from slow_words.py.

You should see the deliberately slow word-counting function as the widest relevant bar. Its width is visual evidence that the script spends most of its sampled time in that function.

You now have before-profile.recording plus flamegraph-before.html as evidence of the original bottleneck. Next, you will replay the same recording as a line-level heatmap.

Inspect the Line-Level Heatmap

The flame graph from the last step showed which function dominates the run. The next question is which operation inside that function consumes the samples.

A line-level heatmap replays the same sampling profiler recording. It overlays sample density on the source code so you can trace the bottleneck to specific lines.

In this step, get ready to:
  • Replay before-profile.recording as a line-level HTML heatmap.
  • Inspect the highlighted lines in slow_words.py.
  • Identify the inefficient word-counting operation.
Replay the recording as a heatmap

The binary recording preserves the samples collected from the slow run. Replaying it creates another view of the same evidence without running the workload again.

  • Before you replay the recording, predict which part of the script the heatmap will emphasize.
  • Create heatmap-before.html from the existing recording by running this command:
python -m profiling.sampling replay --heatmap --browser -o heatmap-before.html before-profile.recording

What does this command do?

  • The replay command reads samples from before-profile.recording.
  • The --heatmap option converts those samples into a source-level visualization.
  • The -o option names the output heatmap-before.html.
  • The --browser option opens the completed report in your default browser.
  • Wait for the replay command to finish.
  • Switch to the browser tab opened by the profiler.

You should see the source files from the recording with sample intensity mapped onto individual lines.

Heatmap Did Not Open?

  • Confirm that before-profile.recording is present in the terminal's current folder.
  • Open heatmap-before.html from the folder containing slow_words.py if the browser tab did not open automatically.

Ask for help if the recording cannot be replayed: help me replay my Python sampling-profiler recording as a heatmap

Trace the heat to slow_words.py

Each highlighted line represents how often that source location appeared in the collected samples. Stronger heat points you toward the operation where the script spends the most time.

How Do You Read the Heat?

Sample counts provide relative evidence about where time accumulates. Focus on the strongest cluster of highlighted lines instead of small differences between cooler lines.

The source view turns a function-level hotspot into a concrete optimization target.

  • Use the report's file index to select slow_words.py.
  • Find the named slow function that appeared as the widest relevant flame graph bar.
  • Scan the highlighted source lines inside that function.
  • Compare the sample intensity across those lines.
  • Identify the inefficient word-counting operation on the hottest lines.

You should see the strongest heat concentrated on the deliberately inefficient word-counting operation inside the slow function.

Cannot Find the Hot Lines?

  • Confirm that the selected source file is slow_words.py.
  • Look for the lines with the largest sample counts if several colors appear similar.

Ask for help interpreting the report: help me identify the hottest lines in my Python heatmap

Confirm both reports tell the same story

The flame graph provides function-level evidence. The heatmap provides line-level evidence inside that function.

  • Before you compare the reports, predict whether their hotspots will agree.
  • Switch back to the browser tab containing flamegraph-before.html.
  • Confirm that the named slow function remains the widest relevant bar.
  • Return to the browser tab containing heatmap-before.html.
  • Confirm that the hottest lines sit inside that same function.
  • Read those lines to identify the inefficient word-counting operation.

The two reports should agree. The flame graph isolates the slow function while the heatmap pinpoints the expensive source lines inside it.

Why Keep Both Reports?

The flame graph shows how runtime spreads across the call stack. The heatmap shows where samples accumulate within the source.

Together they provide visual evidence for the optimization you make next.

✔️ I found the hottest lines

Your heatmap-before.html report now identifies the inefficient word-counting lines in slow_words.py.

ⓧ I'd like to double check the report

No Python code changes in this step. The existing slow_words.py file remains unoptimized.

You should have before-profile.recording, flamegraph-before.html, and heatmap-before.html as evidence from the same slow run.

That is the detective work complete: you now know which source lines are slowing the script. Next, you will fix that hotspot and profile the optimized version.

Optimize and Verify the Hotspot

Your heatmap has narrowed the slowdown to repeated work inside the word-counting function. The before flame graph shows how much runtime that work consumes.

This step turns that evidence into a targeted optimization. You will remove the repeated work. You will measure the new runtime. You will capture an after profile that proves the hotspot shrank.

In this step, get ready to:
  • Replace the repeated word-counting work with a one-pass count.
  • Measure the optimized runtime against the baseline.
  • Capture a new profiler recording for an after flame graph.
Replace the repeated work

The hot operation repeatedly scans the same collection to count each word. A one-pass dictionary count keeps a running total for every word as the script moves through the input once.

This edit is the most hands-on part of the optimization. The heatmap gives you the exact function to change.

  • Return to slow_words.py in the editor you used earlier.
  • Locate the function containing the hottest source line from heatmap-before.html.
  • Refactor the body of that function in one edit. Loop over the words once. Update the current word's dictionary count during each iteration. Return the counts in the same shape as before.
  • Save slow_words.py.

Why one pass is faster

Repeated searches revisit the full word list for every distinct word. A running dictionary count handles each word during a single pass through the input.

The workload now grows with the number of words processed. The script avoids rescanning work it has already completed.

Measure the optimized script

Before you rerun the script, do you expect the elapsed time to stay close to the baseline or drop noticeably?

  • Switch back to the terminal from earlier.
  • Press the Up Arrow until the earlier normal-run command for slow_words.py appears.
  • Press Enter to run the optimized script.

You should see the script finish with a new elapsed runtime.

  • Compare the displayed elapsed time with the baseline timing from earlier.

The optimized elapsed time should be lower than the baseline. That is the first proof that removing the repeated work improved the script.

  • Record the improved value here: your optimized elapsed time.

Runtime did not improve?

Confirm that the hot function loops over the words once. Check that the repeated full-list search identified by the heatmap is gone.

Use the same input.txt file for both measurements. A different input size makes the timing comparison unreliable.

If the result no longer completes correctly, restore the function's original return shape. Keep only the one-pass counting change.

Ask for help with debugging the optimized word-counting function.

Capture and compare the after profile

A faster timer shows that the change helped. A second recording from the sampling profiler shows where the runtime moved after the optimization.

  • Capture the optimized workload in after-profile.recording by running this command:
python -m profiling.sampling run --binary -o after-profile.recording slow_words.py

What does this command do?

  • The run command launches slow_words.py under the sampling profiler.
  • The --binary option stores the collected samples for later replay.
  • The -o option writes the recording to after-profile.recording.

You should see the optimized script complete in the terminal. The new after-profile.recording file should now be available beside slow_words.py.

Recording was not created?

Run the command from the same project location that contains slow_words.py and input.txt.

Confirm that the terminal still uses the Python 3.15 environment that produced before-profile.recording.

Ask for help with creating the optimized sampling-profiler recording.

Before you replay the recording, do you expect the previously wide function bar to keep the same width or become narrower?

  • Generate flamegraph-after.html from the new recording by running this command:
python -m profiling.sampling replay --flamegraph --browser -o flamegraph-after.html after-profile.recording

What does this command do?

  • The replay command converts the saved binary samples without rerunning the workload.
  • The --flamegraph option generates a self-contained HTML flame graph.
  • The --browser option opens the generated report in your default browser.
  • The -o option names the report flamegraph-after.html.
  • Use the browser tabs to compare flamegraph-before.html with flamegraph-after.html.

The bar for the previously slow function should be visibly narrower in the after report. The optimized runtime should also remain lower than the baseline.

You have closed the performance loop. The timer proves the speedup. The two flame graphs show exactly where it came from.

After flame graph did not open?

Confirm that after-profile.recording exists in the current project location. Replay needs that recording to create the HTML report.

If the browser stays closed, locate flamegraph-after.html beside the recording. Open that local file with the browser from earlier.

Ask for help with replaying the optimized recording as a flame graph.

Secret mission

Defend Your Optimization with Evidence

Turn your existing reports into a concise performance evidence brief. Connect the original hotspot to the narrower function bar and the lower elapsed time.

Clean Up Your Resources

Clean Up Your Resources

All Python profiling work in this project stays on your computer. There are no ongoing costs.

Decide whether to keep your local files, set them aside for later, or delete them entirely.

Resources you used:

  • Your source files: slow_words.py plus input.txt.
  • Your profiler recordings: before-profile.recording plus after-profile.recording.
  • Your original reports: flamegraph-before.html plus heatmap-before.html.
  • Your optimized report: flamegraph-after.html.

Keep everything running

No action needed. Choose this option if you want to preserve the reports as evidence of the bottleneck and its improvement.

  • Keep slow_words.py beside input.txt so you can run the workload again.
  • Keep before-profile.recording beside the original HTML reports as your baseline evidence.
  • Keep after-profile.recording beside flamegraph-after.html as your optimized evidence.
  • Open the HTML reports in your browser whenever you want to compare the hotspot before and after optimization.

Pause - I'll come back to this later

Pausing here only means closing the local reports. Every project file stays in place for your next profiling session.

  • Close the browser tab displaying flamegraph-before.html.
  • Close the browser tab displaying heatmap-before.html.
  • Close the browser tab displaying flamegraph-after.html.
  • Leave the seven local project files in their current folder.

Your reports are ready to reopen later. No profiling service needs to stay running in the background.

Delete - I don't want to use this again

Deleting these files is permanent. Your installed Python environment stays available because the commands target only the named project files.

macOS

  • Return to the terminal where you ran slow_words.py.
  • Remove the local project artifacts by running this command:
rm slow_words.py input.txt before-profile.recording after-profile.recording flamegraph-before.html heatmap-before.html flamegraph-after.html
What Does This Command Remove?

The rm command removes only the seven named artifacts from the current folder. It leaves your installed Python environment untouched.

  • Confirm the artifacts are gone by running this command:
ls
What Should I See?

The ls command lists the files that remain in the current folder. You should no longer see any of the seven project files.

Windows

  • Return to the terminal where you ran slow_words.py.
  • Remove the local project artifacts by running these commands:
del slow_words.py
del input.txt
del before-profile.recording
del after-profile.recording
del flamegraph-before.html
del heatmap-before.html
del flamegraph-after.html
What Do These Commands Remove?

Each del command removes one named artifact from the current folder. Your installed Python environment remains available.

  • Confirm the artifacts are gone by running this command:
ls
What Should I See?

The ls command lists the files that remain in the current folder. You should no longer see any of the seven project files.

Cleanup complete. Your Python installation remains ready for another project.

Nice Work!

Nice Work!

That bottleneck has nowhere left to hide. You optimized a Python 3.15 word-counting script with timing results and visual profiles as proof.

You've learned how to:

  • Create a repeatable performance test in slow_words.py with input.txt to measure baseline and optimized elapsed times.
  • Use a sampling profiler to capture before-profile.recording and identify the slow function through a flame graph plus a line-level heatmap.
  • Prove the optimization with a lower elapsed time and a narrower hotspot bar in flamegraph-after.html.
  • Secret Mission: Completed an optional challenge to push your Python profiling skills further.

Ready to quiz yourself?