Python
Argparse
Coding
Optional Arguments
Programming Tips

Argparse optional positional arguments?

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

To make a positional argument optional in Python's argparse, set nargs='?' when calling add_argument(). This tells the parser to consume zero or one value from the command line. If the user omits the argument, the default value is used. For accepting zero or more values, use nargs='*'. For one or more, use nargs='+'.

python
1import argparse
2
3parser = argparse.ArgumentParser()
4parser.add_argument("filename", nargs="?", default="output.txt",
5                    help="Output file (default: output.txt)")
6args = parser.parse_args()
7print(args.filename)
bash
1$ python script.py
2output.txt
3
4$ python script.py report.csv
5report.csv

How nargs Controls Positional Arguments

By default, positional arguments in argparse are required. The nargs parameter changes how many command-line values the argument consumes, and using ?, *, or + makes the argument flexible.

nargs ValueValues ConsumedResult TypeRequired?Behavior When Omitted
(not set)Exactly 1StringYesError
'?'0 or 1String or defaultNoUses default value
'*'0 or moreListNoEmpty list []
'+'1 or moreListYes (at least 1)Error
2 (integer)Exactly 2List of 2YesError

The key insight is that nargs='?' returns a single value (string), while nargs='*' and nargs='+' always return a list, even if only one value is provided.

nargs='?' in Detail

nargs='?' is the most common choice for an optional positional argument. It has three states:

  1. Argument present with a value: the value is used.
  2. Argument absent: the default value is used.
  3. Argument present without a value (only applies to optional flags, not positional): the const value is used.
python
1import argparse
2
3parser = argparse.ArgumentParser(description="File processor")
4parser.add_argument("input", help="Input file to process")
5parser.add_argument("output", nargs="?", default="result.txt",
6                    help="Output file (default: result.txt)")
7args = parser.parse_args()
8
9print(f"Input:  {args.input}")
10print(f"Output: {args.output}")
bash
1$ python process.py data.csv
2Input:  data.csv
3Output: result.txt
4
5$ python process.py data.csv report.csv
6Input:  data.csv
7Output: report.csv

nargs='*' for Zero or More Values

Use nargs='*' when the user may provide any number of values, including none at all. The result is always a list.

python
1import argparse
2
3parser = argparse.ArgumentParser(description="File merger")
4parser.add_argument("files", nargs="*", default=["default.txt"],
5                    help="Files to merge (default: default.txt)")
6args = parser.parse_args()
7
8for f in args.files:
9    print(f"Processing: {f}")
bash
1$ python merge.py
2Processing: default.txt
3
4$ python merge.py a.txt b.txt c.txt
5Processing: a.txt
6Processing: b.txt
7Processing: c.txt

nargs='+' for One or More Values

Use nargs='+' when at least one value is required, but additional values are welcome. This is useful for commands that operate on one or more files.

python
1import argparse
2
3parser = argparse.ArgumentParser(description="File deleter")
4parser.add_argument("files", nargs="+", help="Files to delete")
5args = parser.parse_args()
6
7for f in args.files:
8    print(f"Deleting: {f}")
bash
1$ python delete.py
2usage: delete.py [-h] files [files ...]
3delete.py: error: the following arguments are required: files
4
5$ python delete.py log1.txt log2.txt
6Deleting: log1.txt
7Deleting: log2.txt

Mixing Required and Optional Positional Arguments

A common pattern is one required positional argument followed by an optional one. Order matters: argparse processes positional arguments left to right.

python
1import argparse
2
3parser = argparse.ArgumentParser(description="Database backup tool")
4parser.add_argument("database", help="Database name to back up")
5parser.add_argument("output_dir", nargs="?", default="./backups",
6                    help="Backup directory (default: ./backups)")
7parser.add_argument("--compress", action="store_true",
8                    help="Compress the backup")
9args = parser.parse_args()
10
11print(f"Database:   {args.database}")
12print(f"Output dir: {args.output_dir}")
13print(f"Compress:   {args.compress}")
bash
1$ python backup.py mydb
2Database:   mydb
3Output dir: ./backups
4Compress:   False
5
6$ python backup.py mydb /mnt/backups --compress
7Database:   mydb
8Output dir: /mnt/backups
9Compress:   True

Using const with Optional Flag Arguments

The const parameter becomes relevant when nargs='?' is used with optional (flag) arguments rather than positional ones. It provides a value when the flag is present but no value follows it.

python
1import argparse
2
3parser = argparse.ArgumentParser()
4parser.add_argument("--log", nargs="?", const="app.log", default=None,
5                    help="Enable logging (optional: specify log file)")
6args = parser.parse_args()
7
8if args.log is None:
9    print("Logging disabled")
10else:
11    print(f"Logging to: {args.log}")
bash
1$ python app.py
2Logging disabled
3
4$ python app.py --log
5Logging to: app.log
6
7$ python app.py --log custom.log
8Logging to: custom.log

This three-state behavior (absent / present without value / present with value) is powerful for flags where you want a sensible default but allow override.

Type Conversion with Optional Arguments

The type parameter works with optional positional arguments just like required ones. The conversion is applied to the command-line value, not to the default.

python
1import argparse
2
3parser = argparse.ArgumentParser(description="Retry runner")
4parser.add_argument("command", help="Command to run")
5parser.add_argument("retries", nargs="?", type=int, default=3,
6                    help="Number of retries (default: 3)")
7args = parser.parse_args()
8
9print(f"Running '{args.command}' with {args.retries} retries")
bash
1$ python retry.py deploy
2Running 'deploy' with 3 retries
3
4$ python retry.py deploy 5
5Running 'deploy' with 5 retries
6
7$ python retry.py deploy abc
8usage: retry.py [-h] command [retries]
9retry.py: error: argument retries: invalid int value: 'abc'

A Complete CLI Example

Here is a realistic command-line tool that combines required arguments, optional positional arguments, and optional flags:

python
1#!/usr/bin/env python3
2"""Convert CSV files to JSON format."""
3import argparse
4import csv
5import json
6import sys
7from pathlib import Path
8
9def convert(input_path, output_path, pretty):
10    with open(input_path) as f:
11        reader = csv.DictReader(f)
12        data = list(reader)
13
14    indent = 2 if pretty else None
15    with open(output_path, "w") as f:
16        json.dump(data, f, indent=indent)
17
18    print(f"Converted {len(data)} rows: {input_path} -> {output_path}")
19
20def main():
21    parser = argparse.ArgumentParser(
22        description="Convert CSV files to JSON format"
23    )
24    parser.add_argument("input", help="Input CSV file")
25    parser.add_argument("output", nargs="?", default=None,
26                        help="Output JSON file (default: input with .json extension)")
27    parser.add_argument("--pretty", action="store_true",
28                        help="Pretty-print the JSON output")
29
30    args = parser.parse_args()
31
32    input_path = Path(args.input)
33    if not input_path.exists():
34        print(f"Error: {input_path} not found", file=sys.stderr)
35        sys.exit(1)
36
37    if args.output is None:
38        output_path = input_path.with_suffix(".json")
39    else:
40        output_path = Path(args.output)
41
42    convert(input_path, output_path, args.pretty)
43
44if __name__ == "__main__":
45    main()
bash
1$ python csv2json.py data.csv
2Converted 150 rows: data.csv -> data.json
3
4$ python csv2json.py data.csv output/report.json --pretty
5Converted 150 rows: data.csv -> output/report.json

Comparison with click and typer

argparse is part of the standard library, but third-party libraries like click and typer offer different approaches to optional arguments:

Featureargparseclicktyper
Standard libraryYesNo (pip install)No (pip install)
Optional positionalnargs='?'click.Argument(required=False)typer.Argument(default=...)
Type validationtype=inttype=intPython type hints
Help generationAutomaticAutomaticAutomatic
Subcommandsadd_subparsers()@group.command()app.command()
Learning curveMediumLowLow

For simple scripts and standard-library-only requirements, argparse is the right choice. For larger CLI applications, click or typer reduce boilerplate.

Common Pitfalls

Putting an optional positional before a required one. Argparse processes positional arguments left to right. If an optional nargs='?' argument appears before a required one, the parser may consume the required argument's value for the optional slot, leaving the required argument unsatisfied.

python
1# PROBLEMATIC: optional before required
2parser.add_argument("output", nargs="?", default="out.txt")
3parser.add_argument("input")  # required
4
5# "python script.py data.csv" assigns "data.csv" to output, not input

Always place required positional arguments before optional ones.

Expecting a string from nargs='*'. nargs='*' always returns a list, even if only one value is provided. If your code expects a string, you will get a TypeError when concatenating or comparing.

Forgetting that default is not type-converted. The type function is applied only to command-line input, not to the default value. If you set type=int and default="3", the default will be the string "3", not the integer 3.

python
1# WRONG: default is a string, type=int only applies to CLI input
2parser.add_argument("count", nargs="?", type=int, default="3")
3
4# CORRECT: default is already an int
5parser.add_argument("count", nargs="?", type=int, default=3)

Using nargs='?' when nargs='*' is intended. nargs='?' accepts zero or one value. If the user might pass multiple values, use nargs='*' or nargs='+' instead.

Not providing help text. Argparse auto-generates usage and help output. Without help strings, --help output is cryptic and unhelpful to users.

Summary

  • Set nargs='?' to make a positional argument optional. The user can provide zero or one value.
  • Set nargs='*' for zero or more values (returns a list) and nargs='+' for one or more values (also a list, but errors if none provided).
  • Always provide a default value for optional positional arguments. Without it, the default is None.
  • Place required positional arguments before optional ones to avoid ambiguous parsing.
  • Use the const parameter with nargs='?' on optional flag arguments for three-state behavior (absent, present without value, present with value).
  • The type function applies only to command-line input, not to the default value. Make sure the default is already the correct type.
  • For larger CLI applications, consider click or typer as alternatives that reduce argument-handling boilerplate.

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.