f-Strings
Beginner Python Basics

Python f-Strings

1f-Strings: overview

An f-string is a string literal starting with f whose {braces} hold Python expressions and optional format specs, so values are inserted into text and displayed exactly how you want.

GoalBy the end you can build messages with f-strings, format numbers, dates and columns with format specs, and fix the most common f-string errors.

Before this lesson: Python Variables, Python Strings

2f-Strings: diagram

f"{value:spec}" -> resultf"{value:spec}" -> resultcodevalueoutput{n:.2f}3.141593.14{n:,}12345671,234,567{s:>8}"cat" cat{s:^8}"cat" cat {n:05d}4200042{p:.1%}0.25625.6%{n:b}101010The text after the colon only changes how the value is displayed, never the value itself.
Each row shows a format spec, the value it receives and the text it produces. Combine them, as in {n:>10,.2f}, to build aligned columns of money.

3f-Strings: explained step by step

An f-string is a string that can hold Python expressions. Put the letter f in front of the opening quote, write any value or calculation inside {}, and Python replaces each pair of braces with the result. It is the cleanest way to mix text and values, and it is what you will use for almost every message, report and log line you print. If you are new to text values, start with Python strings and come back.

How f-string interpolation works

Everything inside the braces is a normal Python expression, evaluated at the moment the line runs. That means variables, arithmetic, function calls and method calls all work.

Python
name = "Asha"
items = 3
price = 4.5

print(f"{name} bought {items} items")
print(f"Total: {items * price}")
print(f"Shouting: {name.upper()}, letters: {len(name)}")
print(f"Is it a bulk order? {items >= 10}")

Unlike joining with +, you never convert numbers yourself: the f-string turns each result into text for you. That avoids the classic TypeError from adding a string to an int. For more text tools such as upper(), see Python string methods.

Format specifiers: control how a value looks

After the expression you can add a colon and a format spec. The expression says what to show, the spec says how. The most useful ones for numbers are:

Python
price = 1234.5
ratio = 0.256
order_no = 42

print(f"{price:.2f}")      # 2 decimal places
print(f"{price:,.2f}")     # thousands separator
print(f"{ratio:.1%}")      # percentage (multiplies by 100)
print(f"{order_no:05d}")   # pad with zeros to 5 digits
print(f"{255:b} {255:x} {255:X} {255:o}")   # binary, hex, octal
print(f"{12345.678:e}")    # scientific notation

Read .2f as "2 digits after the point, fixed notation". Rounding happens in the display only; the variable itself is unchanged. For rounding and other number tools see Python numbers.

Align and pad text with width

A number in the spec sets the width. Add <, > or ^ to left-align, right-align or centre inside that width, and put a character before the alignment symbol to use it as padding. This is how you build tidy columns without any extra libraries.

Python
rows = [("Tea", 3.5), ("Coffee", 12), ("Cake", 4.25)]

for name, price in rows:
    print(f"{name:<8}|{price:>8.2f}|")

print(f"{'TOTAL':*^17}")
print(f"{'left':-<10}{'right':->10}")

Strings default to left alignment and numbers to right alignment, so you only need the symbol when you want the opposite.

Width and precision from variables

Format specs can contain their own braces, so the width does not have to be a fixed number. This is handy when column sizes depend on your data.

Python
names = ["Asha", "Benjamin", "Li"]
width = max(len(n) for n in names) + 2
digits = 1

for n in names:
    print(f"[{n:<{width}}]")
print(f"{3.14159:{width}.{digits}f}")

Debug with the = specifier

Add = after an expression and the f-string prints the expression text, an equals sign and the value. It saves you from typing every label by hand while you debug.

Python
total = 19.99
count = 3
print(f"{total=}")
print(f"{count * total=:.2f}")
print(f"{total = }")

Quotes, braces and multi-line f-strings

Since Python 3.12 you may reuse the same quote type inside the braces, and backslashes are allowed there too. In older versions you had to use different quotes. Mixing quotes is still the more readable choice.

To print a literal brace, double it: {{ and }}. For a long message, wrap adjacent f-strings in parentheses; each piece needs its own f.

Python
user = {"name": "Ben", "plan": "pro"}
print(f"{user['name']} is on {user["plan"]}")
print(f"Braces: {{plan}} means {user['plan']}")

message = (
    f"Hello {user['name']},\n"
    f"your plan is {user['plan']}."
)
print(message)

Looking up dictionary values this way pairs well with Python dictionaries.

The !r conversion and formatting dates

!r shows the repr() of a value, which includes quotes and escape characters. It is the quickest way to spot stray spaces or newlines in text. Date and time objects accept strftime codes directly in the spec, so you do not need a separate call (see the datetime module).

Python
from datetime import date

name = " Asha\n"
print(f"{name!r}")
launch = date(2026, 3, 9)
print(f"{launch:%d %B %Y}")
print(f"{launch:%A, %Y-%m-%d}")

f-strings compared with format() and %

All three styles give the same result. f-strings win on readability because the value sits right where it appears in the text.

name, score = "Asha", 91.456
f"{name} scored {score:.1f}"             # f-string (best)
"{} scored {:.1f}".format(name, score)   # str.format()
"%s scored %.1f" % (name, score)         # old % style

Use str.format() only when the template is stored separately from the values, such as text loaded from a file. If a spec fails with a ValueError (for example :.2f on a string), see Python ValueError, and for reading values from users see input and output.

Quick reference: f-Strings

4f-Strings: syntax

f"text {expression}"          # interpolate a value
f"{value:.2f}"                # format spec after the colon
f"{value:>10}"  f"{value:<10}"  f"{value:^10}"    # align in a width
f"{value:,}"  f"{value:05d}"  f"{value:.1%}"     # separators, zero pad, percent
f"{value:{width}.{digits}f}"  # width and precision from variables
f"{expression=}"              # debug: prints  expression=value
f"{value!r}"                  # use repr() of the value
f"{{literal braces}}"         # doubled braces print { and }

5f-Strings: run the example

Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.

Interactive example
customer = "Asha"
items = [("Notebook", 2, 3.5), ("Pen pack", 1, 4.25), ("Stapler", 1, 12.0)]
width = 10
print(f"Receipt for {customer}")
total = 0
for name, qty, price in items:
    line_total = qty * price
    total += line_total
    print(f"{name:<{width}} x{qty} {line_total:>7.2f}")
tax = total * 0.08
print(f"{'Tax (8%)':<{width}}    {tax:>7.2f}")
print(f"{'Total':<{width}}    {total + tax:>7,.2f}")
Expected outputReceipt for Asha
Notebook   x2    7.00
Pen pack   x1    4.25
Stapler    x1   12.00
Tax (8%)         1.86
Total           25.11

7f-Strings: how the example works

Hover or tap a step to highlight its lines in the example above.

  1. Line 1-3A customer name, a list of (name, quantity, price) tuples and a column width of 10 stored in a variable.
  2. Line 4-5The header line uses a plain f-string, then total starts at 0.
  3. Line 6-8The loop unpacks each tuple, computes line_total and adds it to total.
  4. Line 9{name:<{width}} left-aligns the name in 10 characters, taking the width from the variable. {line_total:>7.2f} right-aligns the amount in 7 characters with two decimals, so the numbers line up.
  5. Line 10tax is 8% of the total, a float that has not been rounded yet.
  6. Line 11A string literal can be formatted too. Quotes inside the braces differ from the outer quotes so the f-string stays readable.
  7. Line 12{total + tax:>7,.2f} evaluates an expression, then right-aligns it with a thousands separator and two decimals.

8f-Strings: common mistakes

Forgetting the f prefix

✗ Wrong

name = "Asha"
print("Hello, {name}!")
No error, but the output is Hello, {name}! (the braces are printed literally)

✓ Right

name = "Asha"
print(f"Hello, {name}!")

Without the f before the opening quote Python treats the braces as ordinary characters. Add the prefix every time you want values inserted.

Using :.2f on a string

✗ Wrong

price = "19.5"
print(f"Price: {price:.2f}")
ValueError: Unknown format code 'f' for object of type 'str'

✓ Right

price = "19.5"
print(f"Price: {float(price):.2f}")

Number formats only work on numbers. A value that arrived as text (for example from input()) must be converted with float() first.

Using :d on a float

✗ Wrong

average = 82.5
print(f"Average: {average:d}")
ValueError: Unknown format code 'd' for object of type 'float'

✓ Right

average = 82.5
print(f"Average: {average:.1f}")

d means whole number. Use .0f to show a float without decimals, or .1f for one decimal, or round it yourself with round().

Empty braces in an f-string

✗ Wrong

print(f"Use {} to build a set")
SyntaxError: f-string: valid expression required before '}'

✓ Right

print(f"Use {{}} to build a set")

Inside an f-string, {} must contain an expression. To print literal braces, double them: {{ and }}.

Putting the spec parts in the wrong order

✗ Wrong

total = 1234567.891
print(f"{total:.2,f}")
ValueError: Invalid format specifier '.2,f' for object of type 'float'

✓ Right

total = 1234567.891
print(f"{total:,.2f}")

The order is fixed: fill and alignment, width, thousands separator, .precision, then the type letter. The comma comes before .2f.

9f-Strings: exercises

Write your answer, then press Check. The checks run in your browser.

1. Make the greeting work

Fix the bug Easy

make_greeting(name, unread) should return text like Hi Asha, you have 3 unread messages, but it returns the template unchanged. Fix the function.

Your code
def make_greeting(name, unread):
    return "Hi {name}, you have {unread} unread messages"

print(make_greeting("Asha", 3))

2. Predict the formatted lines

Predict the output Easy

Read the code and type exactly what it prints. Watch the spaces created by the widths. Then press Check.

Read this code
price = 7.5
qty = 12
rate = 0.075

print(f"{price:.2f}")
print(f"[{qty:>5}]")
print(f"[{qty:<5}]")
print(f"{rate:.1%}")
print(f"{qty:03d}")
print(f"{price * qty=}")

2 more exercises → 6 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

Questions learners ask

What is the difference between f-strings and str.format()?

Both use the same format specs, but an f-string evaluates the expressions in place: f"{name} is {age}". With str.format() you pass the values afterwards: "{} is {}".format(name, age). f-strings are shorter and easier to read; str.format() is only needed when the template is defined separately from the data.

How do I print a literal curly brace in an f-string?

Double it. f"{{name}}" prints {name}, and f"{{{name}}}" prints the value of name wrapped in braces. A single { always starts an expression.

How do I round to two decimals in an f-string?

Use the .2f spec: f"{price:.2f}" shows 3.14159 as 3.14. It rounds only the displayed text. If you need the rounded number itself for later calculations, use round(price, 2) instead.

Further reading

10Next lesson

Up nextPython Input and OutputInput and output is how a program talks to its user: input() reads a line the user types and returns it as a string, an…

Related:
Python NumbersPython StringsPython String MethodsPython Input and Output

← Python String Methods · All Python Basics lessons · Practise in the IDE