F-strings (formatted string literals) are one of the most beloved features of modern Python. Introduced in Python 3.6 via PEP 498, they revolutionized how developers concatenate and format strings in the language. Before f-strings, developers used %-formatting or the .format() method — both functional, but far less elegant.
In this complete guide, you'll learn everything from basic syntax to advanced f-string techniques, with practical examples you'll use every day. Whether you're a beginner or an experienced developer, this article will help you master this indispensable tool.
What Are F-Strings and Why Use Them?
An f-string is a string literal prefixed with f or F, allowing you to embed Python expressions directly inside curly braces {}. The interpreter evaluates these expressions at runtime and converts them to strings automatically.
The main advantage of f-strings is readability. Compare the three approaches:
name = "Jane"
age = 28
%-formatting (legacy)
print("My name is %s and I am %d years old." % (name, age))
.format()
print("My name is {} and I am {} years old.".format(name, age))
f-string (modern)
print(f"My name is {name} and I am {age} years old.")
The f-string version is clearly more readable, concise, and intuitive. It's no surprise the Python community adopted them so quickly. According to the official Python documentation, f-strings are "expressions evaluated at runtime, formatted using the __format__ protocol."
Basic Syntax
The fundamental syntax is simple: prepend f before the quotes and use {} to embed variables or expressions:
name = "Carlos"
age = 32
height = 1.85
print(f"Hello, {name}. You are {age} years old and {height}m tall.")
Output: Hello, Carlos. You are 32 years old and 1.85m tall.
You can use any quote style: single ', double ", or triple quotes ''' for multiline strings:
f'String with single quotes'
f"String with double quotes"
f"""Multiline string
with several lines
of formatted text"""
Arithmetic Expressions
a, b = 10, 3
print(f"{a} + {b} = {a + b}") # 10 + 3 = 13
print(f"{a} / {b} = {a / b:.2f}") # 10 / 3 = 3.33
print(f"{a} ** {b} = {a ** b}") # 10 ** 3 = 1000
Expressions and Function Calls
One of the most powerful features of f-strings is evaluating any valid Python expression inside the braces:
# Method calls
name = "john smith"
print(f"Formatted name: {name.title()}")
List expressions
numbers = [1, 2, 3, 4, 5]
print(f"Sum: {sum(numbers)}, Average: {sum(numbers)/len(numbers):.1f}")
Dictionary access
user = {"name": "Maria", "role": "Engineer"}
print(f"{user['name']} works as {user['role']}")
Ternary operator
age = 17
print(f"{'Adult' if age >= 18 else 'Minor'}")
The Real Python tutorial on f-strings shows several advanced examples that expand these possibilities even further.
Working with Dictionaries and Objects
F-strings integrate seamlessly with dictionaries and Python objects:
# Dictionaries
sale = {
"product": "Notebook",
"quantity": 3,
"unit_price": 4599.90,
"customer": "ABC Corp"
}
total = sale["quantity"] * sale["unit_price"]
print(f"Customer: {sale['customer']}")
print(f"Product: {sale['product']} x {sale['quantity']}")
print(f"Total: $ {total:,.2f}")
Objects
class Product:
def init(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def str(self):
return f"{self.name} - $ {self.price:.2f} ({self.stock} units)"
p = Product("Mechanical Keyboard", 289.90, 15)
print(f"{p}") # Uses str automatically
This pattern is widely used in real-world applications, especially when generating reports, rendering server-side templates, or formatting API responses before sending them to clients. The ability to embed complex expressions directly in strings eliminates countless lines of formatting glue code.
Number Formatting
F-strings truly shine when formatting numbers. Following the Python format specification, you can precisely control the output. The syntax {value:format_spec} gives you access to powerful formatting capabilities including rounding, padding, alignment, and locale-aware separators.
Decimals and Rounding
pi = 3.141592653589793
print(f"Pi with 2 decimals: {pi:.2f}") # 3.14
print(f"Pi with 4 decimals: {pi:.4f}") # 3.1416
print(f"Pi with 6 decimals: {pi:.6f}") # 3.141593
Percentages
rate = 0.1785
print(f"Rate: {rate:.1%}") # 17.9%
print(f"Rate: {rate:.2%}") # 17.85%
Currency Formatting
price = 1499.90
print(f"Price: $ {price:,.2f}") # $ 1,499.90
print(f"Price: $ {price:010.2f}") # $ 0001499.90
Large Numbers
population = 8_100_000_000
print(f"Population: {population:,}") # 8,100,000,000
Alignment and Padding
# Alignment: < (left), > (right), ^ (center)
print(f"|{'left':<10}|") # |left |
print(f"|{'right':>10}|") # | right|
print(f"|{'center':^10}|") # | center |
Padding with characters
print(f"{'Python':*^20}") # ***Python****
Date Formatting
Working with dates in f-strings is extremely practical. The key is using Python strftime format codes:
from datetime import datetime
today = datetime.now()
print(f"Date: {today:%m/%d/%Y}") # 05/15/2026
print(f"Time: {today:%H:%M:%S}") # 14:30:00
print(f"Full date: {today:%A, %B %d, %Y}")
print(f"Timestamp: {today:%Y-%m-%d_%H-%M-%S}") # 2026-05-15_14-30-00
The fine-grained control over date formatting makes f-strings a superior choice to the traditional strftime() method in many cases.
Debugging with F-Strings (Python 3.8+)
Python 3.8 introduced a fantastic debugging feature via PEP 536. By adding = after the expression, the f-string displays both the variable name and its value:
name = "Python"
version = 3.12
year = 2026
print(f"{name=}") # name='Python'
print(f"{version=}") # version=3.12
print(f"{year=}") # year=2026
Useful for loop debugging
for i in range(3):
print(f"{i=}, {i**2=}")
i=0, i**2=0
i=1, i**2=1
i=2, i**2=4
This feature advantageously replaces the old print("variable =", variable), making debugging much faster and cleaner.
Multiline F-Strings
For longer text, triple-quoted f-strings are perfect:
name = "John"
role = "Developer"
company = "TechSolutions"
report = f"""
EMPLOYEE REPORT
Name: {name}
Role: {role}
Company: {company}
"""
print(report)
Just be careful with indentation, as it is preserved in the final string.
Nested F-Strings
You can nest f-strings inside f-strings for dynamic formatting:
value = 42.56789
decimals = 2
print(f"Formatted: {value:.{decimals}f}") # 42.57
More complex example
width = 10
fill = ""
text = "Python"
print(f"{text:{fill}^{width}}") # Python
This feature is useful when formatting parameters come from variables or functions.
F-Strings in Python 3.12
Python 3.12 brought significant improvements to f-strings, as documented in the What's New in Python 3.12. Key changes include:
- Quote reuse: You can now use the same quote type inside braces as outside
- More natural nested expressions: The syntax for complex expressions was simplified
- Better performance: The f-string parser was rewritten, making them even faster
# Python 3.12+: compatible quotes inside expressions
people = [{"name": "Anna"}, {"name": "Bob"}]
print(f"Names: {[p['name'] for p in people]}")
# Before 3.12, this raised SyntaxError!
Common Pitfalls
Frequent mistakes when using f-strings:
1. Literal Braces: If you need to display literal braces, double them:
print(f"{{Literal braces}} in f-strings") # {Literal braces} in f-strings
2. Backslash: You cannot use \ inside the expression part of f-strings:
# WRONG - SyntaxError
# print(f"Line break: \n")
RIGHT - evaluate outside
newline = "\n"
print(f"Line break: {newline}")
3. F-Strings in Logging: Avoid f-strings in logging calls. Use % formatting instead:
import logging
WRONG - always evaluates the expression
logging.warning(f"Error for user {user_id}")
RIGHT - lazy evaluation
logging.warning("Error for user %s", user_id)
Using __format__ in Custom Classes
You can customize how your classes are formatted in f-strings by implementing the special __format__ method:
class Currency:
symbols = {"USD": "$", "EUR": "€", "GBP": "£"}
def __init__(self, value, currency="USD"):
self.value = value
self.currency = currency
def __format__(self, spec):
sym = self.symbols.get(self.currency, self.currency)
if spec == "full":
return f"{sym} {self.value:,.2f} ({self.currency})"
return f"{sym} {self.value:{spec}f}" if spec else f"{sym} {self.value:,.2f}"
money = Currency(1250.50, "USD")
print(f"Balance: {money}") # Balance: $ 1,250.50
print(f"Balance: {money:full}") # Balance: $ 1,250.50 (USD)
print(f"Balance: {money:.1f}") # Balance: $ 1250.5
This level of customization allows creating domain-specific formatting for your business.
Performance: F-Strings vs Alternatives
Performance studies show f-strings are consistently faster than alternatives. This is because they are evaluated at compile time and optimized by the CPython interpreter. Benchmarks show f-strings are typically 2x faster than .format() and significantly faster than %-formatting.
For applications that process millions of strings — such as report generators or high-volume logging systems — using f-strings over alternatives can represent significant CPU time savings.
Best Practices
Following community recommendations and the GeeksforGeeks documentation, here are the best practices:
1. Prefer f-strings for simple concatenation: They are more readable and performant than + concatenation or .format().
2. Avoid overly complex expressions: If the expression inside {} gets too long, extract it into a variable first.
3. Use = for temporary debugging: Instead of elaborate prints, use f"{var=}".
4. Watch for sensitive data: F-strings in logs may expose confidential information. Always sanitize first.
# Best practice example
def format_user(user: dict) -> str:
name = user["name"].title()
salary = user["salary"]
bonus = salary * 0.1 if user["performance"] > 8 else 0
return f"Employee: {name} | Salary: $ {salary:,.2f} | Bonus: $ {bonus:,.2f}"
Conclusion
F-strings are without a doubt the best way to work with strings in modern Python. Since their introduction in Python 3.6, they have evolved to include built-in debugging (3.8+), syntax improvements (3.12+), and remain the community's preferred choice.
If you want to master Python completely, check out our complete guide on Python strings and the Python for beginners guide, which cover everything from fundamentals to advanced language topics.
Start using f-strings in your projects today. The difference in code readability and productivity will be immediate. To dive even deeper, the official Python documentation is the best resource for all specification details.