What are f-strings, and how do they compare to `%`-formatting and `.format()`?
Quick Answer
f-strings (`f"{value}"`, PEP 498) embed expressions directly inside string literals and are evaluated **at the point the string is defined** — they're the fastest and most readable option and support format specs (`{value:.2f}`) and debugging output (`{value=}`). `%`-formatting is the oldest, printf-style approach; `str.format()` is more flexible than `%` but more verbose than f-strings. Modern Python code should default to f-strings.
Detailed Answer
The three formatting styles
name, score = "Ada", 97.456
# printf-style (%) -- oldest, C-inspired
"%s scored %.2f%%" % (name, score)
# str.format() -- more flexible, more verbose
"{} scored {:.2f}%".format(name, score)
# f-string (PEP 498) -- modern default
f"{name} scored {score:.2f}%"
All three ultimately support the same format spec mini-language
(:.2f, :>10, :,, :%), but f-strings embed the expression and the
spec directly in the literal, which is both shorter and lets your editor/
type checker see the actual expression being formatted.
Why f-strings are generally preferred
- Any expression is allowed inline, not just a variable name:
f"{price * 1.08:.2f}",f"{obj.method()}". - The
=debug specifier prints both the expression and its value:f"{score=}"→"score=97.456"— handy for quick debugging without writingprint(f"score: {score}")by hand. - Performance: f-strings are compiled to efficient bytecode
(essentially a sequence of
BUILD_STRINGoperations) and are generally faster than%or.format()at runtime. - Readability: the value appears exactly where it's used in the string, rather than being separated into an argument list you have to cross-reference by position.
When you'd still see % or .format()
%-formatting is still common in logging calls (logging.info("x=%s", x)), because the logging module only formats the string if the log level is enabled — deferring the cost — whereas an f-string is evaluated immediately regardless of whether the log line is emitted..format()is useful when the template string itself is not known until runtime (e.g., loaded from a config file or translation catalog), since f-strings must be literal source-code strings, not read from data.
Interview-ready summary: f-strings are the modern default — inline
expressions, a debug = specifier, and better performance than % or
.format(). The main exception is logging calls, where lazy %-style
formatting avoids the cost of building a string that might never be
emitted.