Python f-Strings and String Formatting: A Practical Guide

Python f-strings let you put values, expressions, and formatting rules directly inside a string. They are the clearest and most common way to build readable text from variables in Python 3.12 and later. This article shows how f-strings work, how formatting specs change the output, and where people often make mistakes.

Difficulty: Beginner

Helpful to know first: basic Python variables, strings, and how expressions like arithmetic and function calls work.

How Python f-Strings Turn Values Into Text

An f-string is a string literal that starts with f or F. Inside the string, anything inside curly braces is evaluated as Python code, then turned into text and inserted into the final string.

The main job of an f-string is to save you from manual string joining and repeated calls to str(). Instead of building a sentence in pieces, you write the sentence in its final shape and let Python fill in the values.

Here is the smallest useful example:

Python
name = "Mina"
age = 20

print(f"{name} is {age} years old.")  # Output: Mina is 20 years old.

Python reads the string, finds {name} and {age}, evaluates those names, converts their values to text, and inserts them into the result.

That means an f-string is not a special formatting function you call later. It is processed when Python creates the string object.

Worked Examples of Python f-Strings and Formatting

Using expressions inside braces

You do not need to store every result in a temporary variable. Any valid Python expression can go inside the braces.

Python
price = 12.5
quantity = 4

print(f"Total: {price * quantity}")  # Output: Total: 50.0

The expression price * quantity is evaluated first. Then Python converts the numeric result to a string and inserts it into the sentence.

This is useful for calculations, function calls, and short transformations that make the output easier to read.

Formatting decimals and currency-like values

Raw floating-point numbers often show more digits than you want. A format specifier after a colon lets you control how the value appears.

Python
tax_rate = 0.075
subtotal = 49.99
tax = subtotal * tax_rate

print(f"Tax: {tax:.2f}")  # Output: Tax: 3.75

The .2f format means “show this as a fixed-point number with 2 digits after the decimal point.” This is one of the most common formatting tasks in Python.

If you need correct financial rounding, remember that formatting only changes how a number is displayed. It does not fix floating-point representation issues by itself.

Aligning text in columns

F-strings can pad values so output lines up in neat columns. This is useful in console tables, logs, and simple reports.

Python
product = "Notebook"
price = 4.5

print(f"{product:<12}{price:>8.2f}")  # Output: Notebook         4.50

In this example, <12 left-aligns the product name in a field 12 characters wide, and >8.2f right-aligns the price in a field 8 characters wide with 2 decimal places.

The result is easier to scan than text built with manual spaces.

Using f-strings with dictionaries and attributes

Because the inside of {} is normal Python code, you can access dictionary keys and object attributes directly.

Python
student = {"name": "Ava", "score": 91}

print(f"{student['name']} scored {student['score']} points.")  # Output: Ava scored 91 points.

This keeps the output logic close to the data instead of spreading it across several concatenated strings.

When you work with objects, the same idea applies to attributes:

Python
class User:
    def __init__(self, name, points):
        self.name = name
        self.points = points

user = User("Leo", 18)

print(f"{user.name} has {user.points} points.")  # Output: Leo has 18 points.

F-strings do not need special syntax for dictionaries or attributes. They simply evaluate the expression you write between the braces.

Escaping braces when you want literal curly braces

If you want an actual { or } in the output, you must double it. Single braces are reserved for expressions.

Python
print(f"Set notation looks like {{1, 2, 3}}")  # Output: Set notation looks like {1, 2, 3}

This is important when printing templates, configuration text, or any format that uses braces as part of the content.

How Python Applies Formatting Rules Inside f-Strings

The part after the colon inside a placeholder is called a format specifier. It tells Python how to convert the value into a string. The specifier is not the same thing as the expression itself.

For example, compare these two forms:

Python
value = 3.14159

print(f"{value}")
print(f"{value:.2f}")  # Output: 3.14159\n3.14

The first line uses the default conversion. The second line uses a formatting rule that rounds the display to two decimal places.

The format mini-language can also add signs, zero padding, grouping, and alignment. For example:

Python
n = 42

print(f"{n:06d}")  # Output: 000042
print(f"{n:>6}")   # Output:    42
print(f"{n:<6}")   # Output: 42    

Here, 06d pads an integer with leading zeroes to width 6, while >6 and <6 control alignment.

Formatting is especially useful when you want predictable output for logs, reports, filenames, or user-facing text.

Why f-Strings Are Different From Concatenation and format()

Before f-strings, many beginners used string concatenation or str.format(). Both still work, but f-strings are usually easier to read and write.

ApproachExampleWhat it feels like
Concatenation"Hello " + nameSimple, but awkward once numbers or formatting are involved
format()"Hello {}".format(name)Flexible, but more verbose
f-stringf"Hello {name}"Short, readable, and usually easiest to maintain

For example, this is hard to read with concatenation:

Python
name = "Nina"
score = 9

print("Hello " + name + ", score = " + str(score))  # Output: Hello Nina, score = 9

The f-string version is shorter and easier to read:

Python
print(f"Hello {name}, score = {score}")  # Output: Hello Nina, score = 9

That readability advantage becomes more important as the sentence gets longer or includes formatting rules.

How f-Strings Behave in Detail

F-strings are evaluated left to right, and each placeholder is processed as part of building the string. If an expression has side effects, those side effects happen during string creation.

Python
def next_value():
    print("called")
    return 7

print(f"Value: {next_value()}")
# Output:
# called
# Value: 7

This matters because the expression is not delayed. It runs immediately when the f-string is created.

Another detail: f-strings can contain almost any expression, but they cannot contain statements. You can call a function, index a list, and do arithmetic, but you cannot write a for loop or assignment inside the braces.

Also, an f-string is not magical once it is built. After evaluation, it is just a regular Python str object.

An f-string is often fastest to write and easiest to read, but that does not mean it is always the best choice for every formatting job. The best choice still depends on whether the output is simple text, repeated templates, or data structured for later use.

Predict the Output

Predict the value after formatting and arithmetic

What does this print?

Python
x = 5
y = 2
print(f"{x / y:.1f}")
Show answer

It prints 2.5.

The expression x / y uses true division, so 5 / 2 becomes 2.5. The format specifier .1f keeps one digit after the decimal point, so the final text is 2.5.

Predict the output with doubled braces

What does this print?

Python
name = "Ada"
print(f"{{name}} = {name}")
Show answer

It prints {name} = Ada.

Double braces become a literal brace pair in the output. The placeholder {name} is still evaluated, so Python inserts the value Ada after the literal text.

Common Mistakes With Python f-Strings

Forgetting the f prefix

Without the prefix, Python treats the string as ordinary text. The braces stay in the result instead of being evaluated.

Problem: The string looks like an f-string, but it is missing the prefix, so the placeholder is never evaluated.

Python
name = "Sam"
print("Hello {name}")

Fix: Add f before the opening quote.

Python
name = "Sam"
print(f"Hello {name}")

The fixed version works because Python now evaluates the expression inside the braces.

Using one brace when you meant a literal brace

Single braces start an expression. If you want the character itself, you must double it.

Problem: Python tries to parse {1, 2, 3} as an f-string expression, which is not what the author intended for literal text.

Python
print(f"Set: {1, 2, 3}")

Fix: Double the braces around any literal brace pair.

Python
print(f"Set: {{1, 2, 3}}")

The corrected version works because doubled braces escape to literal braces in the output.

Putting the format specifier in the wrong place

The colon belongs inside the placeholder, after the expression. If you place it outside, Python does not read it as formatting.

Problem: The format rule is written outside the braces, so Python cannot apply it to the value.

Python
price = 3.5
print(f"{price}.2f")

Fix: Put the format specifier after a colon inside the braces.

Python
price = 3.5
print(f"{price:.2f}")

The corrected version works because :.2f is attached to the expression being formatted.

Trying to assign inside the braces

F-strings are for expressions, not statements. Assignment is not allowed inside a placeholder.

Problem: The placeholder contains an assignment, which Python does not allow inside an f-string expression.

Python
count = 3
print(f"Count: {count = 4}")

Fix: Assign the value before the f-string, then format the variable.

Python
count = 4
print(f"Count: {count}")

The fixed version works because the placeholder now contains a valid expression that simply reads the variable.

Find the Bug

A hurried developer wrote this code to print a receipt total. It looks close, but one part is wrong. Find the bug.

Python
item = "Pen"
price = 1.5
qty = 3

print(f"{item}: ${price * qty}.2f")
Show answer

The bug is the placement of .2f. It is outside the braces, so Python treats it as plain text instead of a format specifier.

The intended output is a price formatted to two decimal places.

Fixed code:

Python
item = "Pen"
price = 1.5
qty = 3

print(f"{item}: ${price * qty:.2f}")  # Output: Pen: $4.50

The fixed version works because the format specifier is now attached to the expression inside the braces.

When to Use f-Strings and When to Use Something Else

F-strings are the best default choice for readable string formatting in Python. They are especially good when you already know the values you want to insert and you want the output to stay close to the code that builds it.

Use thisWhen it fits bestWhy
f"...{x}..."Most everyday formattingClear, short, and easy to read
str.format()Reusable templates or older codeUseful when a template is built once and filled later
string.TemplateVery simple named placeholders or safer substitution in some text templatesUses a different placeholder style and can be easier to limit
ConcatenationVery small pieces of textFine for tiny examples, but grows messy fast

Use an f-string when the text is built right where the values are known. Use a different tool when the template must be stored, reused, or handled in a more structured way.

One important comparison is f"..." versus format(). The practical difference is mostly readability. Both can format numbers, align text, and insert values. F-strings usually win because they let you read the sentence in the same order it appears in the code.

Interview Questions About Python f-Strings

Why are f-strings preferred over concatenation?

They are easier to read, reduce repeated str() calls, and let you place expressions and formatting rules close to the text they affect. That makes code shorter and less error-prone.

Can an f-string contain any Python code?

It can contain many expressions, including arithmetic, indexing, attribute access, and function calls. It cannot contain statements like assignments, for loops, or def blocks inside the braces.

What is the difference between {x} and {x!r}?

{x} uses the normal string form of the value. {x!r} uses repr(), which is more detailed and often useful for debugging. If you are printing user-facing text, plain {x} is usually better.

Try It Yourself

Write a small program that prints a neatly formatted receipt line.

  • Create variables for an item name, unit price, and quantity.
  • Compute the total price.
  • Print one line in the form Item: $total.
  • Format the total with two decimal places.

Expected output:

Python
Tea: $7.50

Hint: Multiply the price by the quantity inside the f-string, then attach :.2f to the expression.

Show solution
Python
item = "Tea"
unit_price = 2.5
quantity = 3
total = unit_price * quantity

print(f"{item}: ${total:.2f}")  # Output: Tea: $7.50

Summary

  • Python f-strings insert expressions into text using curly braces.
  • They are usually the most readable way to format strings in Python 3.12+.
  • Use a colon inside the braces to add formatting rules like width, alignment, and decimal places.
  • Double braces when you need literal { or } characters in the output.
  • F-strings evaluate expressions immediately and produce a normal str.
  • Use f-strings for everyday formatting, and reach for other tools only when a reusable template or special placeholder system is a better fit.

Next, learn how format() and the format mini-language let you control padding, alignment, and numeric presentation in more detail.