What’s New ?

The Top 10 favtutor Features You Might Have Overlooked

Read More
Python

Python String Formatting with f-strings, format() and %

Jul 01, 2026 12 Minutes Read Why Trust Us Why you can trust this guide. Written by working engineers and reviewed by our editorial team under a strict editorial policy for accuracy, clarity and zero bias. Kaustubh Saini By Kaustubh Saini Kaustubh Saini Kaustubh Saini
I'm Kaustubh Saini, founder of FavTutor. I have a genuine passion for coding and data science. In my articles, I aim to break down complex topics, share coding insights, and make learning more accessible. When I'm not writing, I'm always exploring ways to enhance your learning experience at FavTutor.
Connect on LinkedIn →
Python String Formatting with f-strings, format() and %

In your first week of Python, you'll try to print a sentence with a number in it. You'll write print("Total: " + 199) and get this error: TypeError: can only concatenate str (not "int") to str. You can fix it with str(), but the fix is ugly, and there's a better way.

String formatting is how you put values inside text. Python has four ways to do it, and one of them, the f-string, is the one you'll use almost every time. This lesson starts there: putting variables into text, rounding numbers, lining up columns, and formatting dates, binary, and hex. After that, we'll look at the older styles you'll still see in other people's code.

Where the + approach falls apart

You can join strings with +, but only strings. If one value is a number, Python stops with an error:

price = 199
print("Total: " + price)
# TypeError: can only concatenate str (not "int") to str

The usual first fix is str():

price = 199
print("Total: " + str(price))
# Output: Total: 199

It works. But every value needs its own str() and its own +. With three or four variables, the line gets long and hard to read. String formatting solves this.

The four ways to format a string

Here's the same sentence built three ways. They all print the same thing:

name = "Asha"
score = 91

print(f"{name} scored {score}")             # f-string
print("{} scored {}".format(name, score))   # str.format()
print("%s scored %d" % (name, score))       # % style
# Output:
# Asha scored 91
# Asha scored 91
# Asha scored 91
The same sentence built with an f-string, str.format(), the % operator, and string.Template

(The fourth way, string.Template, needs an import. It comes later in this lesson.) Use f-strings. They're the shortest to type, the easiest to read, and the fastest to run. You only need to recognize the other styles, because you'll see them in older tutorials and older code.

f-strings, the one to learn first

An f-string is a normal string with an f before the opening quote. Inside it, you put expressions between curly braces { }. Python runs each expression and puts the result into the text. f-strings have been in the language since Python 3.6.

Anatomy of an f-string: the f prefix, the quoted text, and a brace pair holding an expression
city = "pune"
temp = 31.6

print(f"It's {temp} degrees in {city.title()} right now")
# Output: It's 31.6 degrees in Pune right now

Look at the second brace pair. It isn't a plain variable, it calls a method. Any expression works, including maths:

a = 7
b = 3
print(f"{a} times {b} is {a * b}")
# Output: 7 times 3 is 21

The = trick for debugging

Since Python 3.8, you can put = after the expression. It prints the expression and its value together, so you don't type the variable name twice in debug prints:

total = 42 * 3
print(f"{total=}")
# Output: total=126

Printing literal braces

If you need a real { or } in the output, type it twice:

print(f"Use {{braces}} to insert values, like {2 + 2}")
# Output: Use {braces} to insert values, like 4

Quotes inside the braces

If the expression needs quotes, like a dictionary key, use the other quote type:

person = {"name": "Asha"}
print(f"Hello {person['name']}")
# Output: Hello Asha

Single quotes inside, double quotes outside. Python 3.12 allows the same quote type on both, but mixing them works on every version, so it's the better habit.

Showing quotes around a value

Adding !r prints the value with its quotes on. This is useful in error messages because it shows hidden spaces:

name = "Asha "
print(f"Got {name!r}")
# Output: Got 'Asha '

Without !r, you wouldn't see that extra space at the end.

Rounding numbers for display

After the expression, a colon starts a format spec. A format spec is a short instruction that controls how the value is shown. Python's documentation calls this syntax the format specification mini-language, and both f-strings and format() understand it. The spec you'll type most is .2f, which means "show 2 digits after the decimal point":

pi = 3.14159
print(f"pi is about {pi:.2f}")
# Output: pi is about 3.14
A format spec broken into parts: fill character, alignment, width, comma, precision, and type

A format spec only changes how the value prints. The variable itself does not change, so pi still holds 3.14159 after this line. If you need the rounded number for more maths, use round() instead.

A comma groups thousands, and .1% turns a fraction into a percentage:

population = 1428627663
print(f"{population:,}")
# Output: 1,428,627,663

correct = 43
total = 50
print(f"Score: {correct / total:.1%}")
# Output: Score: 86.0%

You can combine specs, so {amount:,.2f} gives both commas and two decimals. One warning about floats:

print(f"{2.675:.2f}")
# Output: 2.67

You'd expect 2.68. But computers can't store 2.675 exactly. They store a value slightly smaller, so the rounding goes down. This comes from how Python stores floats, not from f-strings. For money, where this matters, Python has a decimal module that does exact decimal maths.

Padding and alignment

Put a width in the spec and the value takes up that many characters. < pushes it left, > pushes it right, ^ centers it. That's enough to print a clean receipt:

items = [("Tea", 10.0), ("Samosa", 25.5), ("Thali", 120.0)]
for name, price in items:
    print(f"{name:<10}{price:>8.2f}")
# Output:
# Tea          10.00
# Samosa       25.50
# Thali       120.00
Left, center, and right alignment placing a word inside a fixed-width box

Numbers can be padded with zeros, which is common for invoice and ID numbers:

for n in [3, 27, 154]:
    print(f"Invoice {n:04d}")
# Output:
# Invoice 0003
# Invoice 0027
# Invoice 0154

A character placed before the alignment arrow becomes the fill:

print(f"{' MENU ':*^20}")
# Output: ******* MENU *******

More format spec tricks

The same spec slot can do a few more jobs.

Hex, binary, and octal

The letters x, b, and o print an integer in hex, binary, or octal. A # adds the prefix:

n = 42
print(f"{n:x}  {n:b}  {n:o}")
# Output: 2a  101010  52

print(f"{n:#x}")
# Output: 0x2a

You can combine this with zero-padding. {255:08b} prints 11111111.

Scientific notation and plus signs

print(f"{1234567890:.2e}")
# Output: 1.23e+09

print(f"{5:+}  {-5:+}")
# Output: +5  -5

The + makes positive numbers show their sign. This helps when gains and losses sit in the same column.

Dates inside f-strings

Date and time objects have their own codes, and they work in the same spec slot:

from datetime import date

d = date(2026, 7, 1)
print(f"Report generated on {d:%d %B %Y}")
# Output: Report generated on 01 July 2026

Widths that come from variables

A spec can hold braces of its own, so the width or precision can come from a variable:

width = 12
digits = 3
pi = 3.14159

print(f"{'Tea':<{width}}|")
print(f"{pi:.{digits}f}")
# Output:
# Tea         |
# 3.142

This lets you build tables where the column width comes from the data instead of a guess.

The format() method

Before Python 3.6, this was the main way, and a lot of code written from 2008 to 2016 uses it. Empty braces are slots, and .format() fills them in order:

print("{} scored {}".format("Ravi", 78))
# Output: Ravi scored 78

print("{0}, {0}, {1}!".format("hip", "hooray"))
# Output: hip, hip, hooray!

print("{name} is {age}".format(name="Ravi", age=21))
# Output: Ravi is 21
Arrows from the arguments of format() into the numbered brace slots of the template string

Named slots work well with dictionaries. Unpack one with ** and each key fills its own slot:

person = {"name": "Asha", "age": 21}
print("{name} is {age}".format(**person))
# Output: Asha is 21

Every format spec from the earlier sections works here too, so "{:.2f}".format(3.14159) gives 3.14. There's one case where format() is still the right choice in new code: when the template is stored somewhere else, like a config file or a database. An f-string runs the moment Python reads it. A plain "{name} is {age}" string can be saved, passed around, and filled in later.

The % style

The oldest style comes from the C language. Placeholders start with %, and the values come in a tuple after a % operator:

name = "Meera"
marks = 88
print("%s got %d marks" % (name, marks))
# Output: Meera got 88 marks

%s accepts anything as a string, %d takes an integer, and %.2f takes a float with 2 decimals. It also has named fields, filled from a dictionary:

student = {"first": "Jane", "last": "Doe"}
print("Full name: %(first)s %(last)s" % student)
# Output: Full name: Jane Doe

The % style does not support the format spec mini-language, so the alignment, comma, and date tricks from earlier don't work here. Also watch what %d does to a float:

print("%d" % 9.99)
# Output: 9

Not rounded. Cut off. This can cause real bugs in money code. The % style isn't deprecated, and you'll still see it in older tutorials and in the logging module, but don't start new code with it.

Which style should you pick

Template strings for text you don't control

The fourth way, string.Template, uses $name placeholders:

from string import Template

t = Template("Hi $name, your seat is $seat")
print(t.substitute(name="Asha", seat="12A"))
# Output: Hi Asha, your seat is 12A

It can do less than an f-string, and that's on purpose. An f-string runs any expression inside its braces, so you should never build one from text a stranger typed. Template can only swap in values, nothing else. When the template comes from a user, a form, or a file you didn't write, use Template.

The short version

Decision flow for choosing between f-strings, format(), the % style, and Template
StyleWorks sinceUse it when
f-stringPython 3.6Everything you write yourself. The default.
str.format()Python 2.6The template is stored somewhere and filled in later, or you're unpacking a dictionary.
% operatorPython 1.xReading old code, or format strings for the logging module.
string.TemplatePython 2.4The template text comes from a user or any untrusted place.

If you only remember one row, remember the first.

A receipt program, start to finish

Real programs format values that arrive while the program runs. input() always gives you a string, so convert it to a number first, then format the result when you print:

amount = float(input("Bill amount: "))
tip = amount * 0.10
print(f"Tip: {tip:.2f}, total: {amount + tip:.2f}")
# If you enter 480, this prints
# Tip: 48.00, total: 528.00

That float() call is type casting. You'll use this convert-then-format pattern in most small programs. Here's everything from this lesson in one program: a receipt with aligned columns, two-decimal prices, and a comma-grouped total:

items = [("Masala tea", 2, 10.0), ("Veg thali", 1, 120.0), ("Water bottle", 3, 20.0)]

print(f"{'Item':<14}{'Qty':>4}{'Price':>8}{'Total':>9}")
grand = 0
for name, qty, price in items:
    line = qty * price
    grand += line
    print(f"{name:<14}{qty:>4}{price:>8.2f}{line:>9.2f}")
print(f"{'Grand total':<26}{grand:>9,.2f}")
# Output:
# Item           Qty   Price    Total
# Masala tea       2   10.00    20.00
# Veg thali        1  120.00   120.00
# Water bottle     3   20.00    60.00
# Grand total                  200.00

Every part is one you've already seen: <14 and >9 make the columns, .2f formats the prices, and , groups the total. Change a quantity and run it again. The columns won't move.

Practice exercises

Try each one yourself before you open the solution. Everything you need is above.

Print a receipt line

Given an item and a price, print the price with exactly two decimals.

# Solution
item = "Coffee"
price = 149.5
print(f"{item} costs {price:.2f} rupees")
# Output: Coffee costs 149.50 rupees

Line up a price column

Print three products with names left-aligned to 10 characters and prices right-aligned to 6.

# Solution
for name, price in [("Pen", 10), ("Notebook", 55), ("Bag", 799)]:
    print(f"{name:<10}{price:>6}")
# Output:
# Pen           10
# Notebook      55
# Bag          799

Turn a fraction into a percentage

18 students passed out of 24. Print the pass rate with one decimal.

# Solution
passed = 18
total = 24
print(f"Pass rate: {passed / total:.1%}")
# Output: Pass rate: 75.0%

Show a number in binary and hex

Print 200 in binary, then in hex with its 0x prefix, on one line.

# Solution
n = 200
print(f"{n:b} {n:#x}")
# Output: 11001000 0xc8

Rewrite the old style

Rewrite "%s is %d years old" % ("Ravi", 21) as an f-string.

# Solution
name = "Ravi"
age = 21
print(f"{name} is {age} years old")
# Output: Ravi is 21 years old

Common mistakes

  • Forgetting the f. print("{name}") prints the literal text {name}, braces and all. There's no error, so it's easy to miss.
  • Expecting .2f to change the variable. It only changes the printed text. Use round() when the calculation needs the rounded value.
  • Same quotes inside and outside the braces. f"{person["name"]}" is a SyntaxError before Python 3.12. Mix the quote types.
  • Using %d on floats. It cuts off the decimals, so 9.99 becomes 9. Use %.2f, or better, an f-string.
  • Putting user text into an f-string template. f-strings run the code inside their braces. Use string.Template for text you didn't write.
  • Trusting .2f for money. Floats store 2.675 as slightly less, so it prints 2.67. Use the decimal module for exact money maths.

Frequently asked questions

What is an f-string in Python?

A string with f before the opening quote. Python runs the expressions inside its curly braces and puts the results into the text, so f"{2 + 2}" is the string "4".

Which Python version do f-strings need?

Python 3.6 or newer. The = debugging form needs 3.8, and reusing the same quote type inside braces needs 3.12.

How do I print a number with 2 decimal places?

Add :.2f inside the braces: f"{price:.2f}". The variable keeps its full value; only the printed text is rounded.

What's the difference between an f-string and format()?

An f-string runs immediately, where it's written. format() fills a plain template string, so the template can be stored first and filled later. For strings you type directly into your code, the f-string is shorter.

How do I print curly braces inside an f-string?

Double them. f"{{x}}" prints {x}.

Is % formatting deprecated?

No. It still works and the logging module uses it. But for new code of your own, f-strings are the standard choice.

How do I add commas to a big number?

Put a comma in the format spec: f"{1234567:,}" prints 1,234,567. Combine with decimals as {n:,.2f}.

How do I format a date in an f-string?

Put the date codes in the spec slot: f"{d:%d %B %Y}" prints something like 01 July 2026 for a date or datetime object d.

How do I print a number in binary or hex?

Use the type letters in the spec: f"{n:b}" for binary, f"{n:x}" for hex. Add #, as in {n:#x}, to include the 0x prefix.

Key takeaways

  • An f-string is f"text {expression} text", and it's the default way to build strings since Python 3.6.
  • A format spec after a colon controls the display: .2f for decimals, , for thousands, .1% for percentages. The same slot handles hex, binary, scientific notation, and dates. The value itself never changes.
  • <, >, and ^ with a width line values up into columns, 04d pads numbers with zeros, and widths can come from variables.
  • format() is for stored templates and dictionary unpacking, % lives on in old code and logging, and string.Template is the safe choice for templates users supply.
  • Double braces {{ }} print literal braces, and !r shows a value with its quotes on.

Formatting controls the layout. The text itself often needs cleaning first: trimming spaces, fixing case, replacing characters. That's the job of Python string methods, the natural next lesson after this one.

Kaustubh Saini
About the author

Kaustubh Saini

I'm Kaustubh Saini, founder of FavTutor. I have a genuine passion for coding and data science. In my articles, I aim to break down complex topics, share coding insights, and make learning more accessible. When I'm not writing, I'm always exploring ways to enhance your learning experience at FavTutor. Connect on LinkedIn →
Up nextPython String Slicing with Examples