Join our Newsletter — 33% off our NHI Course

Mutable Default Argument

A mutable default argument is a function parameter whose default value is a list, dictionary, or other object that can change. Python evaluates the default once when the function is defined, so later calls can reuse the same object and accidentally carry state across invocations.

How Mutable Default Arguments Work

A mutable default argument is not re-created on each call. Python binds the default object once at function definition time, so every later call that omits the parameter receives the same underlying object.

This is why the behaviour can look surprising: a function that appends to a default list, updates a default dictionary, or mutates any other default container may preserve state between invocations. The function is behaving consistently with Python’s object model, but not always with the caller’s expectation.

Why the Shared State Can Surprise You

The key issue is object persistence, not just mutability. If the default object is changed inside the function, the next call can observe that earlier change. That makes the bug feel intermittent, because it only appears when the code path performs a mutation rather than a read-only use.

In practice, this often shows up in accumulation patterns, caching shortcuts, or helper functions that were intended to start fresh each time. A default list used as a buffer, for example, may silently grow across calls and mix results that should have remained separate.

Safer Ways to Express “No Default Yet”

The usual pattern is to use an immutable sentinel such as None, then create a new mutable object inside the function when needed. That keeps the function’s default value stable while still allowing each call to work with its own list or dictionary.

That pattern matters because it separates configuration from state. A default argument should usually describe the expected input shape, while the actual mutable working object should be created at runtime when the function needs it.

Common Debugging Clues and Examples

Mutable default bugs often appear as duplicated entries, unexpected carry-over values, or state that seems to “remember” previous requests. They can be especially hard to spot in utility functions because the code may look harmless until the same function is invoked more than once.

A quick diagnostic is to look for function signatures that include list, dict, or set literals as defaults. If the value is meant to vary per call, the default should not be a live container object. This is one of Python’s most famous gotchas because the syntax is concise but the runtime behaviour is easy to misread.

Risk and Threat Considerations

Mutable defaults are a correctness risk because they create hidden shared state across calls. In security-sensitive code, that can turn a small programming mistake into data leakage, request cross-contamination, or unintended privilege and policy carry-over if the function tracks access-related decisions or working state.

Failure mechanism: a function mutates a default container once, and later invocations reuse the same object instead of starting from a clean value.

Impact: outputs can become nondeterministic, tests may pass in isolation but fail in sequence, and state from one operation can influence another in ways that are hard to audit or reason about.

Standards & Framework Alignment

This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.

CIS Controls v8, NIST SP 800-53 Rev 5 and OWASP ASVS set the technical controls, while ISO/IEC 27001:2022 defines the regulatory obligations.

Framework Control / Reference Relevance
CIS Controls v8 CIS-5 — Account Management Mutable defaults can preserve state across executions, affecting how application state and access-related logic behaves.
Recommendation — Review stateful helper functions for unintended persistence that can affect account and access handling.
NIST SP 800-53 Rev 5 CM-6 — Configuration Settings The issue is a configuration-by-code pitfall where defaults must be set to avoid unsafe runtime behaviour.
Recommendation — Set safe defaults for code paths that should not retain mutable state between calls.
OWASP ASVS V15 — Secure Coding and Architecture ASVS addresses implementation patterns that prevent stateful coding mistakes like shared mutable defaults.
V16 — Security Logging and Error Handling Unexpected state carry-over can be detected when behaviour differs across repeated executions and test runs.
Recommendation — Adopt coding patterns that avoid shared mutable defaults in functions handling sensitive data. Log and investigate repeated-call anomalies that suggest hidden shared state in function defaults.
ISO/IEC 27001:2022 A.8.28 — Secure coding Secure coding guidance applies because mutable defaults are a language-level coding hazard that can create defects.
Recommendation — Apply secure coding practices that avoid stateful defaults and other subtle implementation defects.

Practitioner Guidance

What to watch for: review function signatures for mutable literals used as defaults, especially in helpers that collect data, build messages, cache intermediate results, or manage per-call context. If the function must produce fresh state each time, create that object inside the function body rather than in the signature.

Practitioner takeaway: treat mutable defaults as shared state by default, because Python will preserve the same object unless you explicitly choose a new one.