Skip to main content

Default Values and Keyword Arguments

In Lesson 1, James wrote reading_time(words: int, speed: int) -> float. Every call required both arguments: reading_time(1500, 250). But most people read at about 250 words per minute. Typing 250 every time feels redundant.

Emma shows him a small change: speed: int = 250. The = 250 makes the parameter optional. If the caller does not provide a value, the function uses 250. If they do provide one, their value wins.

"So I only specify what is different from normal?" James asks.

"Exactly. Defaults handle the common case. The caller only speaks up when something is unusual."


Default Parameter Values

A default value goes after the type annotation. The pattern is param: type = default:

def reading_time(word_count: int, wpm: int = 250) -> float:
return word_count / wpm

Output:

Now you can call this function three ways:

# Use the default (wpm=250)
time_a: float = reading_time(1500)
print(time_a)

# Override the default with a positional argument
time_b: float = reading_time(1500, 300)
print(time_b)

# Override with a keyword argument (most readable)
time_c: float = reading_time(1500, wpm=200)
print(time_c)

Output:

6.0
5.0
7.5

The first call uses the default wpm=250. The second overrides it with 300. The third uses a keyword argument wpm=200, which makes the call easier to read because you can see which parameter you are setting.

Rule: Parameters with defaults must come after parameters without defaults. def f(x: int = 0, y: int) is a SyntaxError because Python would not know where to put the positional argument.


Keyword Arguments

When a function has several parameters, keyword arguments make calls self-documenting:

def note_summary(title: str, word_count: int, is_draft: bool = False) -> str:
return f"{title} ({word_count} words, draft={is_draft})"

# Positional: you have to remember the order
result_a: str = note_summary("My Note", 350, True)
print(result_a)

# Keyword: the code explains itself
result_b: str = note_summary(title="My Note", word_count=350, is_draft=True)
print(result_b)

Output:

My Note (350 words, draft=True)
My Note (350 words, draft=True)

Both calls produce the same result. The keyword version takes a few more characters but is much easier to read six months later. You can also mix: positional for obvious arguments, keyword for the rest:

result_c: str = note_summary("My Note", 350, is_draft=True)
print(result_c)

Output:

My Note (350 words, draft=True)

Reading AI-Generated Code with an if Preview

When you ask AI to generate a function body using defaults, AI sometimes uses an if statement to handle the two cases.

def format_note(title: str, uppercase: bool = False) -> str:
"""Format a note title, optionally in uppercase."""
if uppercase:
return title.upper()
return title

Output:

print(format_note("My Note"))
print(format_note("My Note", uppercase=True))
My Note
MY NOTE
Control flow preview

The if statement in format_note is a read-only preview. You can read this code and predict its output, but you do not write if statements yourself yet. Control flow (branching and loops) is taught in Phase 3. For now, treat if as something AI may produce that you can understand by reading: "if this condition is true, do this; otherwise, do that."

You can predict the output by reading the if line: when uppercase is True, the function returns the title in all caps. When uppercase is False (the default), it returns the title unchanged. That is all you need to verify AI's work.


Putting It Together

Here is a set of SmartNotes functions using defaults and keyword arguments:

def reading_time(word_count: int, wpm: int = 250) -> float:
return word_count / wpm

def note_summary(title: str, word_count: int, is_draft: bool = False) -> str:
return f"{title} ({word_count} words, draft={is_draft})"

def truncate_title(title: str, max_length: int = 30) -> str:
return title[:max_length]

# Calling with defaults
print(reading_time(1500))
print(note_summary("Types Chapter", 420))
print(truncate_title("A Very Long Title That Exceeds Thirty Characters"))

# Calling with keyword overrides
print(reading_time(1500, wpm=300))
print(note_summary("Types Chapter", 420, is_draft=True))
print(truncate_title("Short", max_length=10))

Output:

6.0
Types Chapter (420 words, draft=False)
A Very Long Title That Exceeds
5.0
Types Chapter (420 words, draft=True)
Short

PRIMM-AI+ Practice: Defaults and Keywords

Predict [AI-FREE]

Look at these function calls. Predict the output for each. Write your predictions and a confidence score from 1 to 5 before checking.

def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"

print(greet("James"))
print(greet("Emma", "Hi"))
print(greet("James", greeting="Hey"))
print(greet(greeting="Yo", name="Bob"))
Check your predictions

Call 1: greet("James") uses the default greeting="Hello". Result: Hello, James!

Call 2: greet("Emma", "Hi") overrides the default positionally. Result: Hi, Emma!

Call 3: greet("James", greeting="Hey") overrides with a keyword argument. Result: Hey, James!

Call 4: greet(greeting="Yo", name="Bob") uses all keyword arguments in reversed order. Python matches by name, not position. Result: Yo, Bob!

If you predicted all four correctly, you understand both defaults and keyword arguments. The tricky one is Call 4: keyword arguments can appear in any order because Python matches them by name.

Run

Create a file with the greet function and all four calls. Run it and compare to your predictions.

Investigate

Write one sentence explaining why greet(greeting="Yo", name="Bob") works even though the arguments are in the opposite order from the signature.

Modify

Add a third parameter punctuation: str = "!" to greet. Update the return to use it: f"{greeting}, {name}{punctuation}". Call the function with punctuation="." and verify the output changes.

Make [Mastery Gate]

Without looking at any examples, write 2 function signatures with meaningful default values:

  1. A function that formats a price with a currency symbol (default: "$")
  2. A function that creates a tag string from a tag name and a prefix (default: "topic")

Write at least one call for each that uses the default, and one that overrides it. Run pyright to confirm zero errors.


Try With AI

Opening Claude Code

If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.

Prompt 1: Discover When Defaults Help

I have this function signature:

def search_notes(query: str, case_sensitive: bool, max_results: int) -> list[str]:
...

Every call requires all three arguments. Which parameters should
have defaults, and what should the defaults be? Explain your
reasoning for each choice.

Read AI's recommendations. It will likely suggest case_sensitive: bool = False and max_results: int = 10. Evaluate: do those defaults match the "common case"? Would you choose different values? Tell AI if you disagree with any default.

What you're learning: You are evaluating whether AI's default choices match real usage patterns. The signature is yours; AI suggests, you decide.

Prompt 2: Keyword Arguments for Readability

Rewrite these three function calls using keyword arguments so
that someone reading the code can understand what each argument
means without looking at the function signature:

note_summary("My Note", 350, True)
reading_time(1500, 200)
truncate_title("Hello World", 5)

Compare AI's rewritten calls to the originals. Which version would you prefer to read in a codebase you maintain? The keyword versions take more characters but carry their meaning with them.

What you're learning: You are practicing the habit of writing self-documenting code. Keyword arguments are a small investment in readability that pays off every time someone reads the code.


Key Takeaways

  1. Default values handle the common case. Writing wpm: int = 250 means callers only specify the reading speed when it differs from 250. The signature stays flexible without becoming verbose.

  2. Keyword arguments make calls self-documenting. reading_time(1500, wpm=200) is clearer than reading_time(1500, 200) because you can see which parameter you are overriding.

  3. Parameters with defaults must come last. Python requires def f(required, optional=default) order. Reversing them causes a SyntaxError.


Looking Ahead

Your function signatures now have types, defaults, and keyword arguments. But the signature only tells pyright and AI what types to expect. In Lesson 3, you add the missing piece: a docstring that tells humans (and AI) what the function does. Together, the signature and docstring form a complete specification.