Python f-strings: A Complete Guide with Real Examples
Introduction
Before Python 3.6, string formatting was awkward:
name = "Alice"
age = 28
# Old ways
print("Hello, %s. You are %d years old." % (name, age))
print("Hello, {}. You are {} years old.".format(name, age))
Both work, but both require you to write the variable names twice — once in the string, once in the argument list. For long strings with many variables, this becomes hard to read and easy to get wrong.
f-strings (formatted string literals) solve this. Introduced in Python 3.6, they let you embed expressions directly inside string literals:
print(f"Hello, {name}. You are {age} years old.")
The variable is written once, right where it appears in the output. The result is shorter, more readable, and faster than the older alternatives.
This guide covers everything you need to know about f-strings — from basic usage to advanced formatting, debugging tricks, and performance.
All examples are tested on Python 3.12.
Basic Syntax
An f-string is a string literal prefixed with f or F. Any expression inside curly braces {} is evaluated and inserted into the string:
name = "Alice"
age = 28
message = f"Hello, {name}. You are {age} years old."
print(message)
Expected output:
Hello, Alice. You are 28 years old.
The prefix can be lowercase f or uppercase F — both work identically. Convention is lowercase.
Expressions Inside f-strings
The content inside {} can be any valid Python expression, not just variable names.
Arithmetic
x = 10
y = 3
print(f"{x} + {y} = {x + y}")
print(f"{x} * {y} = {x * y}")
print(f"{x} / {y} = {x / y:.2f}")
Expected output:
10 + 3 = 13
10 * 3 = 30
10 / 3 = 3.33
Function Calls
name = " alice "
print(f"Stripped and titled: {name.strip().title()}")
print(f"Length: {len(name)}")
print(f"Uppercase: {name.upper()}")
Expected output:
Stripped and titled: Alice
Length: 9
Uppercase: ALICE
Conditionals
score = 85
grade = f"{'Pass' if score >= 60 else 'Fail'}"
print(f"Score: {score} — {grade}")
Expected output:
Score: 85 — Pass
List and Dict Access
colors = ["red", "green", "blue"]
person = {"name": "Bob", "city": "London"}
print(f"First color: {colors[0]}")
print(f"Last color: {colors[-1]}")
print(f"{person['name']} lives in {person['city']}")
Expected output:
First color: red
Last color: blue
Bob lives in London
Note: when accessing dict values inside an f-string, use the opposite quote style from the outer f-string — single quotes inside double-quoted f-strings, or vice versa.
Number Formatting
f-strings use a format spec after a colon : to control how values are displayed.
Decimal Places
pi = 3.14159265358979
print(f"Pi to 2 places: {pi:.2f}")
print(f"Pi to 4 places: {pi:.4f}")
print(f"Pi to 0 places: {pi:.0f}")
Expected output:
Pi to 2 places: 3.14
Pi to 4 places: 3.1416
Pi to 0 places: 3
Thousands Separator
population = 8045311447
salary = 95000.50
print(f"World population: {population:,}")
print(f"Annual salary: ${salary:,.2f}")
Expected output:
World population: 8,045,311,447
Annual salary: $95,000.50
Percentages
ratio = 0.8735
print(f"Accuracy: {ratio:.1%}")
print(f"Accuracy: {ratio:.2%}")
Expected output:
Accuracy: 87.4%
Accuracy: 87.35%
Scientific Notation
tiny = 0.000001234
huge = 123456789.0
print(f"Tiny: {tiny:.2e}")
print(f"Huge: {huge:.3e}")
Expected output:
Tiny: 1.23e-06
Huge: 1.235e+08
Integer Bases
n = 255
print(f"Decimal: {n:d}")
print(f"Binary: {n:b}")
print(f"Octal: {n:o}")
print(f"Hexadecimal: {n:x}")
print(f"Hex (upper): {n:X}")
print(f"Hex with 0x: {n:#x}")
Expected output:
Decimal: 255
Binary: 11111111
Octal: 377
Hexadecimal: ff
Hex (upper): FF
Hex with 0x: 0xff
String Alignment and Padding
Width and Alignment
text = "Python"
print(f"|{text:<20}|") # left-aligned
print(f"|{text:>20}|") # right-aligned
print(f"|{text:^20}|") # centered
Expected output:
|Python |
| Python|
| Python |
Custom Fill Character
print(f"|{text:-<20}|")
print(f"|{text:->20}|")
print(f"|{text:-^20}|")
Expected output:
|Python--------------|
|--------------Python|
|-------Python-------|
Padding Numbers
for i in range(1, 6):
print(f"Item {i:02d}: value")
Expected output:
Item 01: value
Item 02: value
Item 03: value
Item 04: value
Item 05: value
:02d means: integer, minimum width 2, pad with zeros.
Date and Time Formatting
f-strings work directly with datetime objects:
from datetime import datetime
now = datetime.now()
print(f"Date: {now:%Y-%m-%d}")
print(f"Time: {now:%H:%M:%S}")
print(f"Full: {now:%A, %B %d, %Y at %I:%M %p}")
Example output:
Date: 2026-05-16
Time: 14:30:45
Full: Saturday, May 16, 2026 at 02:30 PM
Common date format codes:
| Code | Meaning | Example |
|---|---|---|
%Y | 4-digit year | 2026 |
%m | Month as number | 05 |
%d | Day as number | 16 |
%B | Full month name | May |
%A | Full weekday name | Saturday |
%H | Hour (24h) | 14 |
%I | Hour (12h) | 02 |
%M | Minutes | 30 |
%S | Seconds | 45 |
%p | AM/PM | PM |
Multiline f-strings
For long strings, use triple quotes:
name = "Alice"
age = 28
city = "London"
bio = f"""
Name: {name}
Age: {age}
City: {city}
"""
print(bio)
Expected output:
Name: Alice
Age: 28
City: London
Or use implicit string concatenation for cleaner control over newlines:
report = (
f"Name: {name}\n"
f"Age: {age}\n"
f"City: {city}"
)
print(report)
Expected output:
Name: Alice
Age: 28
City: London
Debugging with =
Python 3.8 introduced the = specifier, which prints both the expression and its value — extremely useful for debugging:
x = 42
items = [1, 2, 3]
print(f"{x=}")
print(f"{items=}")
print(f"{len(items)=}")
print(f"{x * 2 + 1=}")
Expected output:
x=42
items=[1, 2, 3]
len(items)=3
x * 2 + 1=85
You can combine = with format specs:
pi = 3.14159
print(f"{pi=:.3f}")
Expected output:
pi=3.142
This is one of the most underused f-string features. It replaces patterns like print(f"x = {x}") with something shorter and less redundant.
Nested f-strings
The format spec itself can be an f-string expression:
width = 20
text = "Python"
print(f"|{text:^{width}}|")
Expected output:
| Python |
This allows dynamic formatting — the width, precision, or fill character can be computed at runtime:
precision = 3
value = 3.14159
print(f"{value:.{precision}f}")
Expected output:
3.142
Escaping Braces
To include a literal { or } in an f-string, double it:
name = "Alice"
print(f"{{This is in braces}} and {name} is not")
print(f"Set notation: {{1, 2, 3}}")
Expected output:
{This is in braces} and Alice is not
Set notation: {1, 2, 3}
Real-World Examples
Building SQL Queries (Safely)
table = "users"
column = "email"
query = f"SELECT {column} FROM {table} WHERE active = 1"
print(query)
Expected output:
SELECT email FROM users WHERE active = 1
Important: never use f-strings to embed user-supplied data directly into SQL queries — this creates SQL injection vulnerabilities. Use parameterized queries for user input. f-strings are safe for embedding trusted values like column names, table names, or constants.
Generating Reports
from datetime import date
sales = [
{"product": "Laptop", "units": 42, "revenue": 62958.00},
{"product": "Keyboard", "units": 150, "revenue": 10500.00},
{"product": "Monitor", "units": 78, "revenue": 46800.00},
]
print(f"{'Product':<12} {'Units':>8} {'Revenue':>12}")
print("-" * 34)
for item in sales:
print(
f"{item['product']:<12} "
f"{item['units']:>8,} "
f"${item['revenue']:>11,.2f}"
)
total = sum(s["revenue"] for s in sales)
print("-" * 34)
print(f"{'Total':<12} {'':>8} ${total:>11,.2f}")
Expected output:
Product Units Revenue
----------------------------------
Laptop 42 $62,958.00
Keyboard 150 $10,500.00
Monitor 78 $46,800.00
----------------------------------
Total $120,258.00
Log Messages
import logging
from datetime import datetime
logging.basicConfig(level=logging.INFO)
user_id = 1042
action = "login"
ip = "192.168.1.1"
logging.info(f"[{datetime.now():%Y-%m-%d %H:%M:%S}] User {user_id} performed '{action}' from {ip}")
URL Building
base_url = "https://api.example.com"
version = "v2"
resource = "users"
user_id = 42
url = f"{base_url}/{version}/{resource}/{user_id}"
print(url)
Expected output:
https://api.example.com/v2/users/42
Performance Comparison
f-strings are not just more readable — they are also faster than older formatting methods. Here is a benchmark:
import timeit
name = "Alice"
age = 28
percent_time = timeit.timeit(
'"Hello, %s. You are %d." % (name, age)',
globals=globals(),
number=1_000_000
)
format_time = timeit.timeit(
'"Hello, {}. You are {}.".format(name, age)',
globals=globals(),
number=1_000_000
)
fstring_time = timeit.timeit(
'f"Hello, {name}. You are {age}."',
globals=globals(),
number=1_000_000
)
print(f"%-formatting: {percent_time:.3f}s")
print(f".format(): {format_time:.3f}s")
print(f"f-string: {fstring_time:.3f}s")
Example output:
%-formatting: 0.142s
.format(): 0.178s
f-string: 0.073s
f-strings are roughly 2x faster than .format() because the formatting happens at compile time rather than through a function call at runtime.
Common Mistakes
Using Backslashes Inside f-strings
Python does not allow backslash escapes inside the {} expression part of an f-string:
# Wrong — SyntaxError
# print(f"First item: {items[0]\n}")
# Correct — compute the value first
first = items[0]
print(f"First item: {first}")
For newlines inside the string itself (not inside {}), backslashes work fine: f"Line 1\nLine 2".
Quoting Conflicts
person = {"name": "Alice"}
# Wrong — quote conflict
# print(f"Name: {person["name"]}")
# Correct — use opposite quotes
print(f"Name: {person['name']}")
# Also correct — assign first
name = person["name"]
print(f"Name: {name}")
In Python 3.12+, this restriction was relaxed — you can use the same quote style inside {} as outside. But using opposite quotes remains more widely compatible.
f-strings Are Not Templates
f-strings are evaluated immediately when the line runs. They cannot be stored as templates to fill in later:
# This does NOT work as a template
template = f"Hello, {name}" # name is evaluated RIGHT NOW
# For reusable templates, use a function or str.format()
def greet(name):
return f"Hello, {name}"
# Or use Template from the standard library
from string import Template
t = Template("Hello, $name")
print(t.substitute(name="Alice"))
Quick Reference
| Format Spec | Example | Output |
|---|---|---|
:.2f | f"{3.14159:.2f}" | 3.14 |
:, | f"{1000000:,}" | 1,000,000 |
:.1% | f"{0.875:.1%}" | 87.5% |
:>10 | f"{'hi':>10}" | hi |
:<10 | f"{'hi':<10}" | hi |
:^10 | f"{'hi':^10}" | hi |
:05d | f"{42:05d}" | 00042 |
:b | f"{10:b}" | 1010 |
:x | f"{255:x}" | ff |
:%Y-%m-%d | f"{now:%Y-%m-%d}" | 2026-05-16 |
= | f"{x=}" | x=42 |
Wrap-Up
f-strings are the preferred way to format strings in modern Python. They are more readable than % formatting and .format(), faster at runtime, and support a rich set of format specifiers for numbers, dates, alignment, and more.
Three things worth remembering:
- Any Python expression can go inside
{} - Add
:after the expression for format specs —{value:.2f},{value:,},{value:>20} - Use
{variable=}for quick debugging output
For related reading, the list comprehensions guide shows how f-strings and comprehensions combine naturally for generating formatted output. For questions or future tutorial ideas, get in touch via the Contact page.