Files
python-performance-adrs/papers/string-formatting.md
dafit 7efd1368d1 feat: Add 8 domain papers and RULEBOOK.md
Domain papers distilled from python-numbers-everyone-should-know:
- async-overhead: 1,400x sync vs async overhead
- collection-membership: 200x set vs list at 1000 items
- json-serialization: 8x orjson vs stdlib
- exception-flow: 6.5x exception overhead (try/except free)
- string-formatting: f-strings > % > .format()
- memory-slots: 69% memory reduction with __slots__
- import-optimization: 100ms+ for heavy packages
- database-patterns: 98% commit overhead in SQLite

RULEBOOK.md: ~200 token distillation for coding subagents

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 14:31:40 +01:00

2.7 KiB

String Formatting: Domain Exploration

Date: 2026-01-03 Source: python-numbers-everyone-should-know benchmarks Python Version: 3.14.2 (CPython, ARM64 macOS)


Executive Summary

String formatting performance: simple concatenation is fastest for trivial joins, while f-strings offer the best balance of readability and performance for interpolation use cases.


Raw Benchmark Results

Operation Time (ns) Throughput
concat_small 39.1 ns 25.6M ops/sec
f_string 64.9 ns 15.4M ops/sec
percent_formatting 89.8 ns 11.1M ops/sec
format_method 103 ns 9.7M ops/sec

Relative Performance

Method vs f-string
concat_small 1.66x faster
f_string 1.00x (reference)
percent_formatting 0.72x slower
format_method 0.63x slower

Why F-Strings Are Fast

F-strings are parsed at compile time, not runtime:

  1. No method lookup: F-strings don't call .format() at runtime
  2. No tuple creation: % formatting requires (name,) tuple
  3. Specialized bytecode: FORMAT_VALUE and BUILD_STRING are optimized

When to Use Each Method

Concatenation Wins

For 2-3 literal strings with no formatting:

path = base_dir + '/' + filename  # Simpler, faster

% Formatting for Logging

# Deferred evaluation - string built only if debug enabled
logger.debug('Processing %s items', count)

# f-string - string ALWAYS built, then discarded
logger.debug(f'Processing {count} items')  # Wasteful

.format() for Dynamic Templates

template = get_template_from_config()  # Returns 'User: {name}'
result = template.format(name=user.name)

Practical Rules for Coding Agents

Rule 1: Default to F-Strings

# Preferred
message = f'User {user.name} logged in at {timestamp}'

Rule 2: Use Concatenation for Trivial Joins

url = base_url + endpoint  # Fine - simpler and faster

Rule 3: Use join() for Multiple Parts

# Correct - O(n) time
result = ''.join([part1, part2, part3, part4])

# Inefficient - O(n^2) time
result = part1 + part2 + part3 + part4

Rule 4: Keep % for Logging

logger.info('Processed %d records in %.2fs', count, elapsed)

Summary

Scenario Best Choice Reason
Variable interpolation f-string 1.6x faster than .format()
Simple 2-part join Concatenation 1.7x faster than f-string
Building from many parts ''.join() O(n) vs O(n^2)
Logging statements % style Deferred evaluation
Dynamic templates .format() Template flexibility

Benchmark source: python-numbers-everyone-should-know