# Python f-Strings and String Formatting: A Practical Guide

Source: https://devdocs10.com/articles/python/python-f-strings  
By Murat Bo · Published 2026-11-11 · DevDocs10 (https://devdocs10.com)

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.

| Approach | Example | What it feels like |
| --- | --- | --- |
| Concatenation | `"Hello " + name` | Simple, but awkward once numbers or formatting are involved |
| `format()` | `"Hello {}".format(name)` | Flexible, but more verbose |
| f-string | `f"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}")
```

**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}")
```

**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")
```

**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 this | When it fits best | Why |
| --- | --- | --- |
| `f"...{x}..."` | Most everyday formatting | Clear, short, and easy to read |
| `str.format()` | Reusable templates or older code | Useful when a template is built once and filled later |
| `string.Template` | Very simple named placeholders or safer substitution in some text templates | Uses a different placeholder style and can be easier to limit |
| Concatenation | Very small pieces of text | Fine 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.

**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.
