What is the difference between an f-string, str.format and %-formatting in Python?
answer
- Three ways to interpolate, one real difference
- Where does the template text live?
- Literal versus runtime string
- One is compiled inline, two are called
- printf heritage treats its right side as a tuple
basics
~20 sAll three build a str. An f-string is a literal whose expressions are evaluated where it is written, so it cannot be stored as a reusable template; str.format and the % operator format a template string supplied at call time.
solid answer
~50 sAll three produce a `str`, but they differ in where the template lives. An f-string is a *literal*: `f"{qty} x {cost:.2f}"` is compiled into the surrounding code and its expressions are evaluated right there, so there is no template object to pass around. It is the fastest form and the default since 3.6. `str.format` takes a template that is an ordinary runtime string, which is what you need when the text comes from a config file, a message catalogue or a translation table; `str.format_map` is the same thing driven by a mapping. The `%` operator is the oldest, printf-style form and still works, but it has sharp edges: the right-hand side is treated as an argument tuple, so `"%s" % (1, 2)` raises `TypeError` and a single tuple value must be written `(value,)`. Reach for an f-string unless the template itself has to be data.
code
python · 5 linesname, qty, cost = "widget", 3, 4.5
print(f"{name}: {qty} x {cost:.2f}")
print("{}: {} x {:.2f}".format(name, qty, cost))
print("%s: %d x %.2f" % (name, qty, cost))go deeper
Be ready to write the same line all three ways and to say plainly that an f-string is a literal evaluated on the spot. Knowing that f-strings are the modern default and that % needs a tuple on its right is enough here.
Explain the mechanics: the compiler expands an f-string inline, while str.format parses its template on every call. Say when you would still choose str.format — the template coming from config, catalogues or translations — and name str.format_map.
Show the judgement: an f-string is unavailable the moment the text is data, and a data template from an untrusted source is a security decision, not a style one. Be able to justify a codebase-wide convention rather than mixing all three at random.
Own the policy angle. Decide how a team keeps user-facing text translatable and reviewable, where templates are allowed to live, and what enforces the rule — a lint rule beats a style guide. Weigh the cost of migrating legacy % call sites against the bugs it actually prevents.
### Three interpolation styles, one axis that separates them Python has accumulated three general-purpose ways to interpolate values into text, and all three are still fully supported on 3.14. The `%` operator on `str` is the oldest, inherited from C's `printf`. `str.format` arrived with the format mini-language and the `{}` replacement-field grammar. Formatted string literals — f-strings — arrived in 3.6 (PEP 498) and are now the default in idiomatic code. The axis that actually separates them is **where the template lives**. ### An f-string is source code, not data `f"..."` is a *literal*, not a function call and not a value you can hold. The compiler takes the text apart at compile time and emits code that evaluates each embedded expression in the enclosing scope, converts it, applies its format spec and concatenates the pieces. Disassembling one with `dis` shows dedicated conversion, format and string-building opcodes rather than a call into a formatting routine. The consequences follow directly: * There is no template object. You cannot read an f-string from a file, put it in a translation catalogue, or send it over the wire — by the time the value exists, the interpolation has already happened. * The expressions are evaluated **eagerly**, at the point the literal appears. If you build a message you might never emit, you have paid for it. * Because the expressions are compiled like any other code, they can be *anything*: calls, comprehensions, conditional expressions, attribute chains, `await`. * It is the fastest of the three, because nothing parses a template at runtime and no arguments are packed into a call. ### str.format takes a runtime template `"{name}: {qty}".format(name=n, qty=q)` formats a template that is an ordinary string, so the template can come from anywhere — a config value, a database row, a localisation file. That is the reason `str.format` still exists after f-strings, and it is the only reason you should reach for it. `str.format_map` is the same operation driven by a mapping object that is *not* copied into keyword arguments, so a custom mapping (for example one that supplies a default for missing keys) can drive it. The replacement-field grammar is deliberately narrower than Python expressions: a field may name an argument by position or keyword, then walk attributes with `.attr` and index with `[key]` — and that is all. **No calls, no arithmetic, no comprehensions.** That restriction is not cosmetic; it is exactly why a hostile template passed to `str.format` is dangerous but limited, which is a separate question in its own right. ### The % operator is printf-style `"%s: %d" % (name, qty)` is the C heritage. Its conversion set (`%s`, `%d`, `%f`, `%r`, `%x`, `%%`) is its own — it is *not* the format mini-language — and it accepts a width or precision taken from an argument with `%*d`. Its sharp edge is the right-hand side: a tuple is an argument list, so `"%s" % (1, 2)` raises `TypeError: not all arguments converted during string formatting`, and formatting a value that might itself be a tuple requires the awkward `"%s" % (value,)`. A mapping form exists too: `"%(name)s" % row`. ### How to choose 1. **Default to an f-string.** It is the clearest, the fastest, and it keeps the expression next to the text that uses it. 2. **Use `str.format` when the template must be data** — anything read from configuration, catalogues or translations. If that data is user-supplied, treat it as an untrusted template and restrict it. 3. **Leave `%` alone in code that already uses it**, and do not introduce it in new code — its argument-tuple rule causes real bugs, and its conversion set is a second thing to remember. ### Version notes f-strings landed in 3.6; the self-documenting `f"{x=}"` form in 3.8. In 3.12, PEP 701 removed the old parser restrictions so the same quote character may be reused inside a replacement field and expressions may nest and span lines — code written for that grammar will not parse on 3.11 or earlier. On 3.14 there is also a fourth, deliberately different mechanism, the t-string, which produces a template object for deferred rendering instead of a finished `str`; it is not a replacement for any of the three above.
- If f-strings are faster and clearer, why does str.format still exist?Because an f-string is a literal. When the template text is data — a message catalogue, a translation file, a user-configurable report line — there is nothing to write in the source, so you need a function that takes the template at runtime. `str.format` and `str.format_map` are that function. The moment the template is a fixed piece of text you control, the f-string is strictly better.
- What can you write inside an f-string replacement field that you cannot write inside a str.format field?Almost everything. An f-string field is compiled as ordinary Python, so calls, arithmetic, comprehensions, conditional expressions and `await` are all legal. A `str.format` field may only name an argument by position or keyword and then walk attributes with `.attr` and index with `[key]` — no calls at all. That narrower grammar is also what bounds the damage a hostile template can do.
- Why does "%s" % value break when value happens to be a tuple?The `%` operator treats its right-hand operand as the argument list. A tuple is therefore unpacked into several arguments, so a two-element tuple against a one-conversion template raises `TypeError: not all arguments converted during string formatting`. The fix is to wrap it: `"%s" % (value,)`. Neither an f-string nor `str.format` has this rule, which is one reason not to write new `%` code.
An f-string is a sentence you speak on the spot; a str.format template is a form letter you can file away, photocopy and mail later.
saying these in an interview costs you the question
- Claiming an f-string can be stored in a config file as a template
- Saying str.format is deprecated or removed
- Believing f-strings defer evaluation until the string is used
- Thinking % uses the same format mini-language as {}
- Writing "%s" % value without the trailing comma for tuple values
- Assuming str.format fields can call functions