Skip to content
C Codeloom
Python

Python f-Strings Formatting Guide

A complete guide to Python f-strings — basic interpolation, format specifiers for numbers and dates, alignment and padding, debug syntax, and when to choose other formatting methods.

·5 min read · By Codeloom
Beginner 9 min read

What you'll learn

  • How f-strings interpolate Python expressions
  • Formatting numbers, percentages, and dates
  • Aligning and padding text
  • The =-debug syntax for quick prints
  • When to use %-formatting or str.format instead

Prerequisites

  • Basic Python familiarity

Formatted string literals, almost always called f-strings, are the cleanest way to build strings in modern Python. They are fast, readable, and capable of nearly every formatting trick you would ever want. Once you learn the format specifier mini-language, they replace %-formatting and str.format for almost every use.

The Basics

Prefix a string literal with f (or F) and Python evaluates any expression between curly braces.

name = "Ada"
age = 36
print(f"{name} is {age} years old")
# Ada is 36 years old

The expression inside the braces can be anything that evaluates to a value — variables, attribute access, method calls, arithmetic.

items = ["pen", "paper", "ink"]
print(f"You have {len(items)} items: {', '.join(items)}")

Formatting Numbers

After the expression, you can add a colon and a format specifier. For numbers, the common cases are precision, thousands separators, and percentages.

pi = 3.14159265
print(f"{pi:.2f}")     # 3.14 — two decimal places
print(f"{pi:.4f}")     # 3.1416

big = 1234567
print(f"{big:,}")      # 1,234,567 — comma thousands separator
print(f"{big:_}")      # 1_234_567 — underscores instead

ratio = 0.27
print(f"{ratio:.1%}")  # 27.0% — multiplies by 100 and adds %

You can combine specifiers: ,.2f produces 1,234,567.89.

Integers in Different Bases

n = 255
print(f"{n:b}")  # 11111111   — binary
print(f"{n:o}")  # 377        — octal
print(f"{n:x}")  # ff         — hex lowercase
print(f"{n:X}")  # FF         — hex uppercase
print(f"{n:#x}") # 0xff       — with prefix

Useful for debugging bitmasks, dumping bytes, and writing data formats.

Padding and Alignment

A number after the colon sets a minimum width. Use <, >, or ^ for left, right, and centre alignment. Pad with a custom character by putting it before the alignment marker.

name = "Ada"
print(f"[{name:<10}]")   # [Ada       ]
print(f"[{name:>10}]")   # [       Ada]
print(f"[{name:^10}]")   # [   Ada    ]
print(f"[{name:*^10}]")  # [***Ada****]

For numbers, the default alignment is right and the default fill is a space.

for n in [1, 10, 100, 1000]:
    print(f"{n:>4}")
#    1
#   10
#  100
# 1000

You can also pad with zeros, which is the right way to write fixed-width integers.

print(f"{42:05}")   # 00042
print(f"{3.14:08.3f}")  # 0003.140

Sign Control

print(f"{5:+}")    # +5
print(f"{-5:+}")   # -5
print(f"{5: }")    # " 5" — space for positive, minus for negative

Useful when you want positive and negative numbers to line up vertically.

Formatting Dates and Times

datetime objects honour strftime codes inside an f-string.

from datetime import datetime

now = datetime(2024, 6, 15, 9, 30)
print(f"{now:%Y-%m-%d}")        # 2024-06-15
print(f"{now:%I:%M %p}")        # 09:30 AM
print(f"{now:%A, %d %B %Y}")    # Saturday, 15 June 2024

This is shorter than calling now.strftime(...) separately.

The Debug =-Syntax

A small but beloved feature: adding = after an expression prints both the source text and the value.

x = 42
y = 17
print(f"{x=}, {y=}, {x + y=}")
# x=42, y=17, x + y=59

This single trick replaces a huge number of throwaway print(f"x: {x}") statements during debugging. Combine it with a format spec.

import math
print(f"{math.pi=:.3f}")  # math.pi=3.142

Nested Expressions

Anything that’s a valid Python expression can go inside the braces, including function calls and conditional expressions.

balance = -42
print(f"Balance: {'overdrawn' if balance < 0 else 'ok'}")

You can also nest formatting by using an inner f-string to compute the format specifier itself.

width = 10
print(f"{'hi':>{width}}")  # right-aligned in a width set by another variable

Multi-line f-Strings

Combine an f-string with triple quotes for templates.

name = "Ada"
items = ["pen", "paper"]
print(f"""
Hi {name},

Your items:
- {items[0]}
- {items[1]}
""")

For larger templates, string.Template or a real templating library (Jinja2) tends to scale better, but for short cases the triple-quoted f-string is fine.

When Not to Use an f-String

There are a couple of cases where f-strings are not the right tool.

For logging, prefer the logger’s lazy formatting. This avoids building the string when the log level filters it out.

log.info("user %s logged in", user_id)  # preferred
log.info(f"user {user_id} logged in")   # builds the string even if filtered

For building SQL queries or shell commands, never use f-strings to interpolate user input — that is how SQL injection and command injection happen. Use parameterised queries or subprocess arg lists instead.

For internationalisation, where text needs to be extracted into translation files, gettext and friends rely on plain placeholder strings, not f-strings.

A Brief Comparison

name = "Ada"
age = 36

# %-formatting (oldest)
print("%s is %d" % (name, age))

# str.format
print("{} is {}".format(name, age))

# f-string (preferred)
print(f"{name} is {age}")

The f-string version is the shortest, the easiest to read, and slightly the fastest because the formatting happens at compile time.

Wrapping Up

Spend twenty minutes with the format-specifier table and f-strings cover almost every formatting need you will ever have. The combination of plain interpolation, precision control, alignment, and the = debug syntax makes them the default tool — keep %-formatting and str.format in reserve for the few cases where they still belong.