Speed Up a Python CLI with Lazy Imports

Use Python 3.15 lazy imports to measure and reduce CLI startup time.

Introduction

30 Second Summary

A useful tool can feel broken before it does any real work. When its help text takes seconds to appear, every small interaction becomes frustrating.

In this project, you will build a small Python command line tool with one quick command plus one command that needs a heavy library. You will measure its startup delay before using explicit lazy imports to reduce it.

What You'll Build

You will rerun the quick command to watch its startup time drop without losing the heavy command's result.

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

  • A two-command CLI that returns a quick result or loads a heavy library when that work is requested.
  • A browser-based import profile that reveals which library dominates startup, backed by your own before and after timings.
  • A GitHub repository with a README results table that shows the speed improvement you measured on your laptop.
  • An optional Secret Mission to push your startup optimization skills further.

Do I need any experience before starting?

You need basic command line familiarity plus a GitHub account. Step 1 guides you through installing uv and Python 3.15 side by side.

Before We Start

Fast startup work only matters when every timing run uses the same interpreter. Explicit lazy imports require Python 3.15.

The uv tool manages a separate Python version for this project. Your existing system Python stays untouched.

In this step, get ready to:
  • Confirm that the uv command is available in your terminal.
  • Create a slow-python-cli folder on your Desktop.
  • Prepare a uv-managed Python 3.15 environment inside the new folder.
Install uv

Uv downloads the interpreter version your project needs. First, check whether the command is already available.

  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type Terminal on macOS or PowerShell on Windows.
  • Press Enter to open the terminal.
  • Check whether uv is available by running this command:
uv --version

What does this command do?

The --version option asks uv to print its installed version. Any uv version number confirms that the command is available.

✔️ I see a uv version

Uv is ready to manage the project interpreter. That removes the first setup hurdle.

ⓧ Command not found

Your terminal cannot find uv yet. Install it with the official standalone installer for your platform.

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 uv installation script from Astral. Your shell runs that script to place uv on your machine.

  • Close the terminal window after the installer finishes.
  • Use system search to reopen Terminal.
  • Confirm that uv is now available by running:
uv --version
What does this command confirm?

A version number confirms that your new terminal can find the uv executable.

You should see a uv version number in the terminal.

Windows

  • Install uv by running the official installer command:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
What does this command do?

The command downloads the uv installation script from Astral. PowerShell runs that script with a temporary execution policy override.

  • Close the PowerShell window after the installer finishes.
  • Use system search to reopen PowerShell.
  • Confirm that uv is now available by running:
uv --version
What does this command confirm?

A version number confirms that your new PowerShell session can find the uv executable.

You should see a uv version number in the terminal.

Still seeing command not found?

Close every terminal window before opening a fresh one. This gives your shell another chance to load the updated command path.

Check the final installer message for a command that updates your shell environment. Run the command it shows.

Use the official uv installation guide if your machine blocks the standalone installer.

help me make the uv command available in my terminal

Create the project environment

The project needs a dedicated folder before uv can pin its interpreter. You will keep that folder on your Desktop so it stays easy to find.

macOS

  • Move to your Desktop by running this command:
cd ~/Desktop

What does this command do?

The cd command changes the terminal location. The ~/Desktop path points to the Desktop inside your home folder.

  • Create the slow-python-cli folder by running these commands:
mkdir slow-python-cli
cd slow-python-cli
pwd

What do these commands do?

  • The first command creates the slow-python-cli folder.
  • The second command moves your terminal into that folder.
  • The final command prints your current location so you can confirm the move.

You should see a path ending in Desktop/slow-python-cli.

Windows

  • Move to your Desktop by running this command:
Set-Location -Path "$env:USERPROFILE\Desktop"

What does this command do?

The command changes the PowerShell location to the Desktop inside your Windows user profile.

  • Create the slow-python-cli folder by running these commands:
New-Item -ItemType Directory -Path ".\slow-python-cli"
Set-Location -Path ".\slow-python-cli"
Get-Location

What do these commands do?

  • The first command creates the slow-python-cli folder.
  • The second command moves PowerShell into that folder.
  • The final command prints your current location so you can confirm the move.

You should see a path ending in Desktop\slow-python-cli.

Your machine may already have another Python version. A quick check tells you which starting point applies before uv installs the project copy.

  • Check the Python version currently available in your shell by running:
python --version

What does this command check?

The command asks the Python executable currently on your command path to print its version. This check does not change your existing installation.

✔️ I see version 3.15 or higher

Your current interpreter understands the language feature used later in the project. The uv-managed setup below still gives the folder its own predictable Python 3.15 environment.

ⓧ I see an older version

Your existing interpreter stays in place. Uv installs Python 3.15 beside it.

The project pin below directs future uv commands to the newer interpreter.

ⓧ Command not found

Your shell does not currently have a Python executable named python.

Uv supplies the missing interpreter through the shared setup below.

  • Install the latest Python 3.15 patch release through uv by running:
uv python install 3.15

What does this command do?

Uv downloads a managed Python 3.15 interpreter. It selects the latest available patch release in that version line.

The download can take a minute on a slower connection. Terminal progress confirms that the installation is still moving.

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

What does this command do?

Uv records Python 3.15 as the default interpreter request for this folder. The setting lives in a .python-version file.

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

  • Create the project environment with Python 3.15 by running:
uv venv --python 3.15

What does this command do?

Uv creates an isolated environment in the .venv folder. The --python 3.15 option selects the managed interpreter you installed.

You should see confirmation that uv created a virtual environment with Python 3.15.

Python setup failed?

Confirm that uv --version works in the same terminal. A missing uv command means your shell path still needs to refresh.

Confirm that the terminal path ends in slow-python-cli. The pin belongs inside that project folder.

Check your internet connection if the interpreter download stops before completion.

help me fix my uv-managed Python 3.15 setup

Activate and verify Python 3.15

Activating the virtual environment makes the plain python command use the interpreter inside .venv. This keeps every future timing run tied to the same version.

macOS

  • Activate the project environment by running:
source .venv/bin/activate

What does this command do?

The command updates the current shell so python points to the interpreter inside .venv.

Your terminal prompt should now include (.venv).

Windows

  • Activate the project environment by running:
.venv\Scripts\activate

What does this command do?

The command updates the current PowerShell session so python points to the interpreter inside .venv.

Your PowerShell prompt should now include (.venv).

Before you check, which Python version do you expect the activated environment to report?

  • Verify the active interpreter by running:
python --version

What does this command confirm?

The command prints the version of the active Python interpreter. The (.venv) prompt shows that the project environment supplies it.

You should see output beginning with Python 3.15. Your terminal should still be inside the slow-python-cli folder.

Seeing an older Python version?

Check that your terminal prompt includes (.venv). Activate the environment again if that marker is missing.

Confirm that you created .venv from inside the slow-python-cli folder.

help me make python --version report Python 3.15

That is the foundation locked in. Your project now has its own Python 3.15 interpreter ready for the CLI you build next.

Build and Time the CLI

Your terminal now uses Python 3.15. The interpreter that supports this project is ready.

A useful speedup needs an honest before measurement. You will build a two-command command line interface with a heavy import on its startup path. You will then record how long the quick command takes.

In this step, get ready to:
  • Initialize the Python project with its heavy dependency.
  • Build quick and heavy commands with an eager import.
  • Measure the quick command across fresh Python processes.
Set up the project dependency

The project needs dependency metadata so every run uses the same environment. SymPy serves as the heavy library whose import cost you will measure.

  • Initialize the current project directory by running this command:
uv init --bare

What did uv create?

The command creates pyproject.toml for the project metadata. It keeps the project on the Python interpreter selected earlier.

You should see confirmation that the project was initialized. The project directory now contains pyproject.toml.

Project initialization failed?

Check that the terminal is still inside the empty project directory from the previous step. An existing pyproject.toml means the directory is already initialized.

If the command cannot find uv, return to the terminal where you verified it earlier.

help me diagnose my uv project initialization error

  • Add the heavy dependency to the project by running this command:
uv add sympy

What does this command do?

Uv adds sympy to the dependencies in pyproject.toml. It also creates the project environment and lockfile.

Future project commands now run with the declared dependency available.

You should see dependency resolution and installation activity. The project directory now contains uv.lock alongside pyproject.toml.

Dependency installation failed?

Confirm that your internet connection is active. Uv needs to download the declared packages during the first installation.

Check that pyproject.toml exists in the directory shown by your terminal prompt.

help me fix my SymPy installation

Build the two-command CLI

Both commands live in one Python module. The module imports SymPy immediately, so the quick path loads the dependency even though its handler never uses it.

  • Create cli.py inside the project directory using your code editor.
  • Add the following code to cli.py:
import argparse
import sympy

# Print a fast result without using the heavy dependency
def run_quick(_args):
    print("Quick command finished.")

# Use SymPy only in the command that needs symbolic math
def run_heavy(args):
    print(sympy.factorint(args.number))

# Build the two-command interface and dispatch the selected handler
def main():
    parser = argparse.ArgumentParser(description="Measure eager import startup.")
    commands = parser.add_subparsers(required=True)

    quick_parser = commands.add_parser("quick", help="Run the lightweight command.")
    quick_parser.set_defaults(handler=run_quick)

    heavy_parser = commands.add_parser("heavy", help="Factor an integer with SymPy.")
    heavy_parser.add_argument("number", type=int)
    heavy_parser.set_defaults(handler=run_heavy)

    args = parser.parse_args()
    args.handler(args)

if __name__ == "__main__":
    main()

What does this code do?

  • The module-level import sympy loads the heavy dependency before argument parsing begins.
  • The quick parser dispatches to run_quick(). That handler only prints a short confirmation.
  • The heavy parser accepts an integer. Its handler uses sympy.factorint() to factor that value.
  • The final two lines call main() when you run the file directly.
  • Save cli.py.
  • Run the quick command by entering this command in the project terminal:
uv run python cli.py quick

What should I see?

You should see Quick command finished. in the terminal. This proves that argument parsing selected the quick handler.

Good progress. Your quick command works even though the process also imports the library needed by the heavy command.

Quick command did not run?

Check that cli.py is saved in the same directory as pyproject.toml.

Compare the indentation below main() with the complete file. Python uses indentation to group each parser operation.

help me fix my quick command

✔️ Awesome, I've got everything!

Your quick command runs successfully. Make sure cli.py is saved before you measure it.

ⓧ I'd like to double check the full code

Compare your complete cli.py file with this version:

import argparse
import sympy

# Print a fast result without using the heavy dependency
def run_quick(_args):
    print("Quick command finished.")

# Use SymPy only in the command that needs symbolic math
def run_heavy(args):
    print(sympy.factorint(args.number))

# Build the two-command interface and dispatch the selected handler
def main():
    parser = argparse.ArgumentParser(description="Measure eager import startup.")
    commands = parser.add_subparsers(required=True)

    quick_parser = commands.add_parser("quick", help="Run the lightweight command.")
    quick_parser.set_defaults(handler=run_quick)

    heavy_parser = commands.add_parser("heavy", help="Factor an integer with SymPy.")
    heavy_parser.add_argument("number", type=int)
    heavy_parser.set_defaults(handler=run_heavy)

    args = parser.parse_args()
    args.handler(args)

if __name__ == "__main__":
    main()
Measure the eager baseline

One run can be distorted by other work on your computer. A small benchmark starts the CLI in five fresh Python processes. It records the median elapsed time in baseline.txt.

  • Create benchmark.py inside the project directory using your code editor.
  • Add the following timing code to benchmark.py:
import statistics
import subprocess
import sys
import time

# Run the quick command in a fresh interpreter for every measurement
command = [sys.executable, "cli.py", "quick"]
runs = []

# Capture the complete startup time across five runs
for _ in range(5):
    started = time.perf_counter()
    subprocess.run(command, check=True, capture_output=True, text=True)
    runs.append(time.perf_counter() - started)

# Record the median so later results can use the same format
median = statistics.median(runs)
record = f"Baseline quick-command startup: {median:.3f} seconds\n"
print(record, end="")
with open("baseline.txt", "w", encoding="utf-8") as file:
    file.write(record)

How does the benchmark work?

  • The command list uses the same Python interpreter that runs the benchmark.
  • Each subprocess.run() call starts cli.py from scratch. This includes the eager SymPy import.
  • The median represents the middle measurement from the five runs.
  • The final lines display the result. They also save the same result in baseline.txt for the later comparison.
  • Save benchmark.py.

Before you run the benchmark, how much startup time do you expect the unused eager import to add to the quick path?

  • Measure the quick command by running this command:
uv run python benchmark.py

What should I see?

You should see a line beginning with Baseline quick-command startup: followed by your measured time in seconds. The same line is now stored in baseline.txt.

That is your first real performance result. You now have a repeatable baseline for the eager version of the CLI.

Benchmark failed?

Check that benchmark.py and cli.py are saved in the same project directory.

If the benchmark reports a subprocess failure, run the quick command again. Fix that command before repeating the benchmark.

help me debug my startup benchmark

✔️ Awesome, I've got everything!

Your benchmark printed a median startup time. Confirm that baseline.txt is listed in your project directory.

ⓧ I'd like to double check the full code

Compare your complete benchmark.py file with this version:

import statistics
import subprocess
import sys
import time

# Run the quick command in a fresh interpreter for every measurement
command = [sys.executable, "cli.py", "quick"]
runs = []

# Capture the complete startup time across five runs
for _ in range(5):
    started = time.perf_counter()
    subprocess.run(command, check=True, capture_output=True, text=True)
    runs.append(time.perf_counter() - started)

# Record the median so later results can use the same format
median = statistics.median(runs)
record = f"Baseline quick-command startup: {median:.3f} seconds\n"
print(record, end="")
with open("baseline.txt", "w", encoding="utf-8") as file:
    file.write(record)

Your two-command CLI now has a recorded eager-import baseline. Next, you will trace the startup imports and identify which one dominates the run.

Profile the Startup Imports

Your baseline timing proved that the quick command pays a noticeable startup cost. Now you need to trace that delay to a specific import.

An import profile records how long each module takes to load. Python's -X importtime option gives you the evidence behind the delay.

In this step, get ready to:
  • Capture an import-time profile from the quick command.
  • Render the profile as a treemap in your browser.
  • Identify the heavy library as the dominant startup cost.
Capture the import-time profile

Python writes its import timing statistics to standard error. This output stream stays separate from the quick command's normal result.

  • Switch back to the terminal from the previous step.
  • Find the CLI invocation from your previous timing command in the terminal history.
  • Enter its source file plus the quick-command argument as your CLI invocation for the quick command.
  • Capture the quick command's import profile by running this command:
python -X importtime [[QUICK_COMMAND="your CLI invocation for the quick command"]] 2> importtime.txt

What Does This Command Capture?

  • The -X importtime option asks Python to measure every module loaded during startup.
  • The 2> importtime.txt redirection saves the timing statistics in importtime.txt. The file remains inside your local project directory.
  • The quick command still triggers the eager heavy-library import. This preserves the slow version you want to investigate.
  • Confirm the terminal prints the quick command's usual result.
  • Confirm the terminal returns to its prompt after the process completes.

Profile File Missing?

Check that your value includes the CLI source file plus its quick-command argument. Leave python -X importtime at the start of the command.

Make sure the command ends with 2> importtime.txt. Run it from the same local project directory that contains your CLI.

Ask for help with my import-time profiling command

Visualize the import timings

The saved profile contains rows of module timings. A treemap converts those rows into areas that make expensive import branches easier to compare.

  • Open the Python Import Time Graph in your browser.
  • Load importtime.txt from your local project directory using the page's file control.

You should see a treemap filled with labelled import branches. Larger areas represent imports with a greater share of the recorded startup time.

You have turned the raw import timings into a map you can inspect.

Treemap Not Appearing?

Confirm you selected importtime.txt from the local project directory. Selecting the CLI source file gives the visualizer the wrong input.

If the profile is empty, rerun the profiling command. Wait for the quick command to finish before loading the file again.

Help me load my Python import profile

Identify the startup bottleneck

Before you inspect the treemap, which import do you expect to occupy the largest branch?

  • Locate the branch labelled with the import name of your heavy library.
  • Compare its area with the remaining import branches.
  • Match that label to the eager heavy-library import in your CLI source file.

The heavy library should occupy the largest share of the treemap. This confirms that its eager import dominates the quick command's startup cost.

What Has the Profile Proved?

Your baseline measurement showed the size of the delay. The import profile now identifies where that delay comes from.

The quick command never uses the heavy library. Its eager import still forces Python to load that library during startup.

You now have measured proof of the startup bottleneck. Next, you will change when the heavy library loads.

Make the Heavy Import Lazy

Your import-time profile pinpointed the heavy library as the biggest startup cost for the quick command.

The quick command never uses that library. An eager import still loads it before command selection.

The fix uses explicit lazy imports in Python 3.15. This syntax waits until code first uses the imported name.

In this step, get ready to:
  • Change the heavy library's eager import to Python 3.15 explicit lazy-import syntax.
  • Rerun the quick command with the same timing method from your baseline.
  • Record the improved startup time for the final README comparison.
Defer the heavy import

At module startup, an eager import executes the heavy library immediately. The lazy prefix creates a deferred binding that resolves when code first accesses it.

Your command-selection logic stays untouched. Only the heavy library's import statement changes.

  • In your code editor, open the CLI source file inside the local project directory.
  • Find the heavy-library import statement identified by your import-time profile.
  • Prefix only the existing statement with lazy so it begins with lazy import or lazy from.
  • Save the CLI source file.

What Changed?

  • The lazy prefix tells Python to postpone executing the heavy library.
  • The quick command leaves the imported name untouched. The library therefore stays deferred on that path.
  • A module-level reference to the imported name would trigger the library during startup. Keep its first use inside the heavy command.
Measure the quick command again

Startup timing varies slightly between runs. Using the same command keeps the import strategy as the changed condition.

Before you rerun it, decide whether you expect the quick command to start faster.

  • Switch back to the terminal from your baseline timing test.
  • Press the Up Arrow key until the baseline timing command appears.
  • Press Enter to run the recalled command.

You should see the quick command's normal output followed by its elapsed time. The after-change time should be lower than the recorded baseline.

That is the payoff you measured: the quick path reaches its result without waiting for the heavy library.

Timing Barely Changed?

  • Confirm the terminal still uses Python 3.15 if a syntax problem appears.
  • Check that the import statement begins with lazy import or lazy from.
  • Search the module-level code for an early reference to the imported name if the timing remains close to the baseline.

A module-level reference forces the deferred import to execute during startup.

Ask for help diagnosing the unchanged startup time.

A few runs separate a stable improvement from one unusually fast launch.

  • Rerun the same timing command twice more through your terminal history.

You should see the after-change results cluster below the original baseline.

  • In your code editor, create an after-change timing record beside the existing baseline timing record inside the local project directory.

You should see the new timing record beside the baseline record in your project view.

  • Enter a representative improved result in the new record: your improved elapsed time.
  • Save the after-change timing record.

Your local project directory now preserves both measurements for a direct before-and-after comparison.

Your quick path now skips the heavy library's startup cost. Next, you'll run the heavy command to prove that the deferred import still activates when needed.

Verify the Heavy Command

The quick path now avoids loading the heavy library during startup. Your after-change timing shows the resulting speed improvement.

The remaining question is whether the optimized CLI still preserves its heavy capability. The heavy command must trigger the deferred import when it first uses the library.

In this step, get ready to:
  • Run the heavy command through the optimized CLI.
  • Trace the heavy library as it loads on first use.
  • Capture evidence that the heavy command still works.
Run the heavy command

An explicit lazy import keeps the heavy library unresolved until the heavy command first uses its imported name. A successful run confirms that the optimization has preserved the CLI's heavy path.

  • Switch back to the CLI source file from earlier.
  • Find the code that runs for the heavy subcommand.
  • Read its output logic to identify the exact result the command is designed to produce.
  • Switch back to the terminal in the local project directory from earlier.
  • Press Up Arrow until your after-change quick-command invocation appears.
  • Replace the quick subcommand with the heavy subcommand you identified.

Before you run it, do you expect the deferred library to load successfully when the heavy command reaches its first use?

  • Run the reconstructed heavy-command invocation by pressing Enter.

You'll see the exact result defined by the heavy command. The process completes without an import failure.

Good. The optimized CLI still delivers the heavy command's intended result.

Why This Proves First Use

The quick command never touches the lazy name. Python can finish that path without loading the heavy library.

The heavy command reaches code that uses the imported name. Its successful output proves that Python resolved the deferred import at that point.

Heavy Command Failed?

  • Confirm that the terminal still uses the uv-managed Python 3.15 interpreter from earlier.
  • Check that the heavy library remains declared in the project dependency configuration.
  • Compare the lazy import statement with the optimized source you saved in the previous step.

help me diagnose why the heavy command fails on first use.

Trace the first-use import

The successful result confirms that the library remains usable. Import-time tracing provides visible evidence that the heavy command loads it during execution.

  • Press Up Arrow until the import-time profiling invocation from earlier appears.
  • Replace its quick subcommand with the heavy subcommand.

Before you run it, do you expect the heavy library to appear in this trace?

  • Run the reconstructed profiling invocation by pressing Enter.

You'll see import-time lines for the heavy library. You'll also see the heavy command's intended result.

What the Trace Confirms

The optimized quick command can finish without importing the heavy library. This heavy-command trace includes the library because the command reaches its first use.

Together, the two paths prove the optimization works as intended. Lightweight work stays fast while heavy functionality remains available.

Heavy Library Missing From the Trace?

  • Confirm that the restored profiling invocation still includes the import-time option from your earlier profile.
  • Check that you replaced the quick subcommand with the heavy subcommand.
  • Confirm that the heavy command reaches code that uses the imported library.

help me verify when my lazy import loads.

  • Keep the successful profiling invocation visible in the terminal.
  • Scroll until the heavy library's import-time lines are visible.
  • Position the heavy command's intended result in the same terminal view.

That completes the compatibility check. Your quick command stays fast while the heavy command retains its full capability.

Next, you'll publish the repository to GitHub. Your README will present the baseline timing beside the improved result.

Publish Your Timing Results

Your optimized CLI now starts the quick command without loading the heavy library. The heavy command still works because Python 3.15 defers the import until first use.

Those results currently live on your computer. Publishing the project on GitHub turns your measurements into a record that other people can inspect.

In this step, get ready to:
  • Create a GitHub repository for your optimized CLI.
  • Upload the complete project with its timing records.
  • Document the measured startup improvement in README.md.
Create the GitHub repository

A repository gives your CLI a shareable home with a visible history. Initializing it with a README also gives you a place to explain the measured result.

  • Open GitHub's new repository page in your browser.
  • Complete the GitHub sign-in flow if your account details are requested.
  • Select your personal account from the Owner menu.
  • Enter python-lazy-import-cli in the Repository name field.
  • Enter A measured Python 3.15 explicit lazy import example. in the Description field.
  • Select Public as the repository visibility.
  • Set Add README to On.
  • Click Create repository.

Your repository page opens with README.md in the file list. That first file confirms the repository is ready for your project.

Good progress. Your measured optimization now has a public destination.

Repository creation blocked?

Check that the repository name contains no spaces. Choose a different name if another repository in your account already uses it.

If GitHub asks you to sign in again, complete that flow before returning to the repository form.

help me create my GitHub repository

Upload the optimized project

Uploading the project creates a commit containing the optimized CLI and its evidence. The dependency configuration lets another developer reconstruct the environment without uploading your installed packages.

  • Click Add file above the repository file list.
  • Click Upload files.
  • Switch to Finder on macOS or File Explorer on Windows.
  • Return to the local project directory from the earlier steps.
  • Select the CLI source file plus the project dependency configuration.
  • Select both timing records plus the import-time profile.
  • Select the heavy-command verification record.
  • Drag the selected project files onto the GitHub upload page.

What belongs in the repository?

The repository needs the source file that contains the explicit lazy import. It also needs the dependency configuration that selects Python 3.15 and declares the heavy library.

Your two timing records provide the comparison. The import profile plus the heavy-command verification preserve the evidence behind your conclusion.

  • Confirm the staged file list contains the CLI source file.
  • Confirm the staged file list contains the dependency configuration.
  • Confirm the staged file list contains both timing records.
  • Confirm the staged file list contains the import-time profile plus the heavy-command verification.
  • Enter Upload optimized CLI and timing results in the Commit message field.
  • Choose the option that adds the upload to the current branch.
  • Click Commit changes or Propose changes according to the button GitHub displays.

You should see the uploaded project files beside README.md on the repository page. The file list proves the optimized CLI and its evidence are published together.

Missing project files?

Return to Add file plus Upload files if one of the timing records or verification files was omitted. Commit the missing file as a second upload.

Avoid uploading an installed Python environment. The dependency configuration already records what the project needs.

help me complete my GitHub upload

Document the measured improvement

A README uses Markdown to explain what a repository contains. Your README will connect the eager import problem to the exact startup times you measured.

  • Click README.md in the repository file list.
  • Click the pencil icon in the upper-right corner of the file view.
  • Replace the starter text with documentation for your CLI.
  • Add a title that names the Python CLI startup optimization.
  • Write an overview that states the quick command originally paid the heavy import cost at startup.
  • Explain that Python 3.15 explicit lazy imports defer the heavy library until first use.
  • Create a Quick command subsection with the exact invocation from your timing records.
  • Create a Heavy command subsection with the exact invocation from your heavy-command verification.

What should the timing table show?

Create columns named Measurement, Import behavior, and Elapsed time. These columns connect each observed time to the import strategy that produced it.

  • Use the baseline timing record for the Before row. Label its import behavior as eager.
  • Use the after-change timing record for the After row. Label its import behavior as lazy.
  • Copy each elapsed time exactly as you recorded it. Your own measurements are the result this repository demonstrates.
  • Add the Before row with the elapsed time from your baseline timing record.
  • Add the After row with the elapsed time from your after-change timing record.
  • Write a result sentence that compares the two measured startup times.
  • Add a note confirming that the heavy command still works through its deferred first-use import.
  • Click Preview above the README editor.

You should see a rendered README with command descriptions and a two-row timing table. Both elapsed-time cells should contain your recorded measurements.

  • Click Commit changes....
  • Enter Document lazy-import timing results in the Commit message field.
  • Choose the option that commits the README to the current branch.
  • Click Commit changes.

Timing table not rendering?

Return to the README editor if the rows appear as plain text. Check the spacing around each column separator before using Preview again.

Compare each elapsed time with its original timing record. A copied value should preserve the same unit and precision.

help me fix my README timing table

Before you check the repository homepage, which timing do you expect a reader to identify as faster?

  • Return to the repository main page by selecting python-lazy-import-cli near the upper-left corner.

You should see the complete project file list above a rendered README. The timing table should show a faster After result while the heavy-command note confirms that deferred loading still works.

That completes the evidence trail. Your repository now shows the original bottleneck plus the measured effect of fixing it.

Secret mission

Validate Your Startup Benchmark

A single fast run can hide normal timing variation. Repeat your optimized benchmark five times to test whether the startup improvement holds up across multiple launches.

Clean Up Your Resources

Clean Up Your Resources

Choose whether to keep your project available, pause your work, or remove its artifacts. This local project has no running process or ongoing cost.

Resources you used:

  • A GitHub repository containing the optimized CLI and its timing results.
  • A local project folder containing README.md and the CLI source file.
  • A managed Python 3.15 interpreter installed through uv.

Keep everything running

Leave everything in place if you want your measurements to remain easy to revisit. The project stays available without creating an ongoing cost.

  • Keep your GitHub repository online as a record of the eager-to-lazy import improvement.
  • Keep your local project folder for future timing experiments.
  • Keep the uv-managed Python 3.15 interpreter ready for other explicit lazy-import projects.

Pause - I'll come back to this later

There is no background process to shut down. Choose this option when you want to leave the project untouched between sessions.

  • Close the terminal session you used for the CLI.
  • Close the import-time visualizer in your browser.
  • Leave the GitHub repository online.
  • Keep the local project folder on your computer.
  • Keep the uv-managed Python 3.15 interpreter installed.

Delete - I don't want to use this again

Deleting these artifacts removes your project record. Save the timing table somewhere else first if you want to keep that evidence.

Delete the GitHub repository
  • Return to the GitHub repository page from earlier.
  • Open the repository settings page.
  • Scroll to the repository deletion controls.
  • Choose the permanent repository deletion option.
  • Complete the confirmation steps shown by GitHub.
  • Return to your GitHub repositories list.

You should no longer see the project repository in your repositories list.

Delete the local project files
  • Open Finder on macOS or File Explorer on Windows.
  • Locate the local project folder that contains README.md.
  • Check that the folder also contains the CLI source file.
  • Move the project folder to Trash or Recycle Bin.
  • Empty Trash or Recycle Bin.

You should no longer see the project folder in its original location.

Remove the uv-managed interpreter

The interpreter is the only runtime this project added. Removing it leaves uv available for future projects.

  • Return to the terminal from earlier.
  • Use the Python uninstall workflow in uv to remove its managed Python 3.15 interpreter.
  • Use the managed-interpreter listing in uv to check the remaining installations.

Python 3.15 should no longer appear among the interpreters managed by uv.

Nice Work!

Nice Work!

You did it! Your two-command Python CLI now starts its quick command without loading the heavy library.

You've learned how to:

  • Measure startup performance for the quick command. You captured a baseline timing. You traced the heavy library as the dominant import cost.
  • Apply Python 3.15 explicit lazy imports so the quick command skips the heavy library during startup. You confirmed that the heavy command loads its dependency on first use.
  • Publish the optimized CLI in a GitHub repository. Your README.md explains the change with a before-and-after timing table.
  • Secret Mission: Complete an optional challenge that extends the same profiling and optimization workflow.

Ready to quiz yourself?