Python f-strings: A Complete Guide with Real Examples

· 9 min read

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:

CodeMeaningExample
%Y4-digit year2026
%mMonth as number05
%dDay as number16
%BFull month nameMay
%AFull weekday nameSaturday
%HHour (24h)14
%IHour (12h)02
%MMinutes30
%SSeconds45
%pAM/PMPM

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 SpecExampleOutput
:.2ff"{3.14159:.2f}"3.14
:,f"{1000000:,}"1,000,000
:.1%f"{0.875:.1%}"87.5%
:>10f"{'hi':>10}" hi
:<10f"{'hi':<10}"hi
:^10f"{'hi':^10}" hi
:05df"{42:05d}"00042
:bf"{10:b}"1010
:xf"{255:x}"ff
:%Y-%m-%df"{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:

  1. Any Python expression can go inside {}
  2. Add : after the expression for format specs — {value:.2f}, {value:,}, {value:>20}
  3. 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.