What are f-strings, and how do they compare to `%`-formatting and `.format()`?

4 minbeginnerfundamentalsstringsformatting

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 writing print(f"score: {score}") by hand.
  • Performance: f-strings are compiled to efficient bytecode (essentially a sequence of BUILD_STRING operations) 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.

Related Resources