1.2.13 · D5Introduction to Programming (Python)

Question bank — f-strings — embedding expressions

1,505 words7 min readBack to topic

Before you start, one picture to keep in your head: an f-string is a story with blanks. The f stamp says "fill the blanks", the { } marks where a blank is, and whatever you write inside a blank is code that runs, not text that prints. Every question below pokes at one of those three parts.


True or false — justify

Every answer explains why — a bare "true/false" earns nothing here.

Adding an f prefix to a string with no braces changes what it prints.
False — the f only activates brace-substitution; with no { } there is nothing to substitute, so f"hi" and "hi" produce the identical text.
f"{name}" and "{name}".format(name=name) produce the same output.
True — both replace the placeholder with the value; the f-string just reads the variable directly from scope instead of taking it as a .format() argument. See str.format() method.
Inside braces you may write a full assignment like {count = count + 1}.
False — braces need an expression (something with a value), and an assignment is a statement; this raises a SyntaxError. (The lookalike {count=} with nothing after the = is the unrelated debug form.)
The text outside the braces is always printed exactly as typed.
Mostly true, with one catch: a doubled brace {{ or }} collapses to a single literal { or }, so those four characters do not print as typed.
An f-string is a runtime feature that reads variables when the line runs, not when the file is written.
True — the expressions in braces are evaluated at the moment execution reaches that line, using the current values in scope; change a variable first and the f-string reflects the new value.
f"{3/0}" will still build a string as long as the syntax is valid.
False — the expression inside runs like any Python code, so 3/0 raises ZeroDivisionError before any string is produced; valid syntax is not the same as a valid result.
The format spec after : changes the value itself, not just how it looks.
False — the spec only controls the text representation (decimals, padding, commas); the underlying value is untouched. See Format specification mini-language.
F"{x}" (capital F) is a syntax error.
False — both lowercase f and uppercase F are valid prefixes and behave identically.

Spot the error

Each line has a bug or a surprise. Name it and give the fix.

print("Total: {price}") — expected the number, got the word.
The f prefix is missing, so {price} is plain text; it prints Total: {price}. Fix: print(f"Total: {price}").
f"He said {"hello"}" breaks on older Python.
The inner " closes the string early on pre-3.12 parsers. Fix: use the other quote inside — f"He said {'hello'}". See Strings — literals and quotes.
f"The set is {1, 2, 3}" — you wanted the braces to print.
A single { starts an expression, so this actually evaluates the tuple (1, 2, 3) and prints The set is (1, 2, 3). To print literal braces, double them: f"The set is {{1, 2, 3}}".
f"{ x = 5 }" — trying to set and show x.
Assignment isn't an expression, so this is a SyntaxError. Do the assignment on its own line first, then use f"{x}" (or f"{x=}" for the debug form).
msg = f"Age: {age}" printed the old age even though you changed age later.
The f-string was evaluated when that line ran, capturing the value at that instant into msg; changing age afterwards can't reach back into an already-built string. Rebuild the f-string after the update.
print(f"{total:.2}") gave odd output for a whole number.
.2 without a type letter means "2 significant digits", not "2 decimal places". For fixed decimals use .2f. See Format specification mini-language.
f"{items[0}" — a stray character sneaks in.
The } is missing (or ] and } are tangled); the brace never closes, giving a SyntaxError. Every { needs a matching } after a complete expression: f"{items[0]}".

Why questions

These test whether you understand the mechanism, not just the syntax.

Why does f"{a + b}" correctly show a number without you calling str()?
Each brace is implicitly wrapped in format(expr, spec), which defaults to str(expr); the conversion happens automatically after the expression is evaluated. See str() and type conversion.
Why can you put a method call like {word.upper()} inside braces but not a for loop?
A method call returns a value (it's an expression); a for loop is a statement with no value, and braces only accept things that evaluate to a result.
Why do you double the braces to print a literal brace instead of using a backslash?
The f-string mini-syntax reserves { and } as its own markers, so it uses doubling as its escape rule; \{ isn't recognised — the parser only knows {{{.
Why does {x=} show x=9 while {x} shows just 9?
The trailing = triggers the self-documenting debug form, which prints the source text of the expression plus its value; without the = you get only the value.
Why does the literal + in f"{a} + {b}" not get computed?
It sits outside any braces, so it's ordinary text; only characters inside { } are treated as code. The interpreter scans left to right and copies out-of-brace characters verbatim.
Why is f"{price:,}" clearer than manually inserting commas with string slicing?
The comma spec delegates thousands-grouping to the built-in formatter, which handles sign, length and edge cases correctly — reinventing it by hand invites off-by-one and negative-number bugs. See Format specification mini-language.
Why do we say the f-string "reads left to right"?
The interpreter walks the string once, copying literal characters and, at each {, pausing to parse and evaluate the expression up to the matching } before continuing — a single ordered pass, so order of appearance = order of substitution.

Edge cases

The scenarios the happy-path examples skip.

What does an empty f-string f"" produce?
The empty string "" — no braces, nothing to fill, so it behaves like any empty string literal.
What does f"{''}" (empty string expression inside braces) produce?
An empty string again — the expression '' evaluates to the empty string, which contributes no visible characters, so the whole result is "".
What happens with f"{None}"?
It prints Noneformat(None) falls back to str(None), which is the text "None"; there's no special "blank" behaviour for None.
Does f"{x}" work when x is a list, like [1, 2, 3]?
Yes — any object gets str()-ified, so a list prints as [1, 2, 3]; there's no requirement that the value be a string or number.
What if a brace pair contains only whitespace, e.g. f"{ }"?
It's a SyntaxError — an empty (or whitespace-only) expression has nothing to evaluate; the braces must contain a real expression.
Can nested braces appear, as in a format spec that uses a variable width f"{val:{width}}"?
Yes — the inner {width} is itself evaluated to supply the field width at runtime, letting the format spec be computed rather than hard-coded. See Format specification mini-language.
What is printed by f"{{}}" — is it a blank filled with nothing?
It prints {} — the doubled braces are two escapes ({{{, }}}), so there is no expression slot here at all, just two literal characters.

Recall One-line summary of every trap on this page

The f activates filling · braces hold expressions, not statements · code inside braces runs (and can crash) · doubling escapes literal braces · the spec after : changes looks, not value · substitution happens when the line executes.

Connections

  • f-strings — embedding expressions — the parent note these traps are built against.
  • str() and type conversion — why no manual str() is needed inside braces.
  • str.format() method — the older sibling these questions often contrast with.
  • Format specification mini-language — everything after the :.
  • Variables and scope — where brace expressions read their values.
  • Strings — literals and quotes — the quote-nesting trap.
  • print() function — the usual consumer of the finished string.