subprocess
Popen
run
Python
programming

What is the difference between subprocess.popen and subprocess.run

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

subprocess.run() and subprocess.Popen() both start external processes in Python, but they solve different levels of the same problem. run() is the high-level convenience API for "run this command and wait for the result," while Popen() is the lower-level API for fine-grained process control.

If you only need to execute a command and collect its exit status or output, run() is usually the right choice. Reach for Popen() when you need streaming I/O, background execution, custom pipelines, or more manual lifecycle control.

subprocess.run() Is the Simple, Blocking API

run() starts a process, waits for it to finish, and returns a CompletedProcess object:

python
1import subprocess
2
3result = subprocess.run(
4    ["python", "--version"],
5    capture_output=True,
6    text=True,
7    check=False,
8)
9
10print(result.returncode)
11print(result.stdout)
12print(result.stderr)

This is the modern default for straightforward command execution. It is readable, safe when you pass an argument list, and covers the most common use case.

You can also ask it to raise an exception automatically if the command fails:

python
subprocess.run(["false"], check=True)

That raises CalledProcessError if the exit code is non-zero.

subprocess.Popen() Gives You Manual Control

Popen() starts the process and returns immediately with a process object:

python
1import subprocess
2
3proc = subprocess.Popen(
4    ["python", "-c", "import time; time.sleep(2); print('done')"],
5    stdout=subprocess.PIPE,
6    stderr=subprocess.PIPE,
7    text=True,
8)
9
10stdout, stderr = proc.communicate()
11print(proc.returncode)
12print(stdout)

This is more verbose, but it lets you decide when to wait, how to read output, whether to poll, and how to combine several child processes.

A Good Rule of Thumb

Use run() when all of these are true:

  • you want to wait for completion immediately
  • you do not need interactive communication while the process runs
  • you want the simplest API

Use Popen() when you need at least one of these:

  • process starts in the background
  • stdout or stderr must be handled incrementally
  • the child process needs interactive input
  • you are building a pipeline or supervising several processes

That rule covers most real projects.

run() Is Built on Top of Popen()

Conceptually, run() is a convenient wrapper around the lower-level machinery. It handles the common "start, wait, capture, return" workflow for you so you do not have to write the same boilerplate over and over.

That is why many code examples that used Popen() years ago are clearer today with run().

Streaming Output Is Where Popen() Shines

Suppose you want to watch output line by line while a process is still running. run() is not designed for that. Popen() is:

python
1import subprocess
2
3proc = subprocess.Popen(
4    ["python", "-u", "-c", "for i in range(3): print(i)"],
5    stdout=subprocess.PIPE,
6    text=True,
7)
8
9for line in proc.stdout:
10    print(f"child said: {line.strip()}")
11
12proc.wait()

This pattern is useful for long-running jobs, build tools, and programs where you want live progress rather than waiting for the final output buffer.

Background Processes and Polling

Popen() also makes sense when the parent process has other work to do:

python
1import subprocess
2import time
3
4proc = subprocess.Popen(["python", "-c", "import time; time.sleep(5)"])
5
6while proc.poll() is None:
7    print("still running")
8    time.sleep(1)
9
10print("finished with", proc.returncode)

That style would be awkward with run() because run() blocks until completion.

Common Pitfalls

The biggest pitfall is using Popen() when run() would be simpler. That adds complexity without any benefit.

Another common mistake is forgetting to consume or redirect pipes with Popen(). If the child process writes enough data to a pipe that nobody reads, the process can block.

People also mix shell=True into both APIs without thinking about security. If command parts come from user input, passing an argument list is usually safer than sending one shell string.

Finally, remember that run() is blocking by design. If you expect the parent code to continue immediately, run() is the wrong tool.

Summary

  • 'subprocess.run() is the high-level API for running a command and waiting for the result.'
  • 'subprocess.Popen() is the low-level API for manual process management.'
  • Use run() for simple one-shot commands.
  • Use Popen() for streaming output, background work, pipelines, and interactive control.
  • Prefer the simplest API that matches the behavior you actually need.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.