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. 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 (for example, loaded from a config file or translation catalog), since f-strings must be literal source-code strings, not read from data.

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