PYTHON • COLLECTIONS AND STRINGS

f-Strings & Formatting — Format strings with f-strings and format specifiers

Master Python's most readable way to embed variables, expressions, and precise formatting directly inside strings.

Historical Context & Motivation

From Python's earliest days, developers needed ways to insert variable values into text. Imagine you want to print "Hello, Alice! You scored 95%." If the name and score come from variables, how do you glue them into the sentence? Over the years, Python offered several answers to this question, each one improving readability and safety. The journey from clunky %-formatting to elegant f-strings mirrors Python's broader philosophy: code should be easy to read and easy to write.

1991
Python 1.0 — %-Formatting
Python borrowed C-style % formatting (e.g., "Hello %s" % name). It worked but was hard to read when many variables were involved.
2008
Python 3.0 — str.format()
The str.format() method replaced % placeholders with curly braces, making complex formatting clearer and more powerful.
2015
PEP 498 Proposed
Eric V. Smith authored PEP 498, proposing formatted string literals (f-strings) to let developers embed expressions directly inside string literals at write time.
2016
Python 3.6 — f-Strings Arrive
f-strings became official in Python 3.6. Prefixing a string with f allows inline expressions inside {} braces, evaluated at runtime. They are faster and more readable than older methods.
2023
Python 3.12 — Enhanced f-Strings
Python 3.12 relaxed restrictions, allowing nested quotes, backslashes inside f-string expressions, and multi-line expressions, making f-strings even more flexible.

The core question f-strings answer is straightforward: how can you build dynamic strings in Python with maximum readability, minimal boilerplate, and precise control over formatting? Let's find out.

Core Principles & Definitions

An f-string (short for formatted string literal) is a string that begins with the letter f or F before the opening quote. Inside that string, any Python expression placed between {} curly braces is evaluated at runtime and its result is inserted into the string. A format specifier is an optional mini-language after a colon inside the braces that controls how the value appears—decimal places, alignment, padding, and more.

1

The f-Prefix

Place f before the quote mark to activate expression evaluation: f"Hello, {name}". Without the prefix, the braces are treated as literal text.
2

Expressions Inside Braces

You can put any valid Python expression inside {}—variables, arithmetic, function calls, method calls, even conditionals. Python evaluates the expression and converts the result to a string.
3

Format Specifiers

Add a colon after the expression to control display: {price:.2f} rounds to two decimal places. The mini-language controls width, alignment, padding, type, and grouping.
4

Runtime Evaluation

f-strings are not templates stored for later. They are evaluated the instant Python encounters them, using the current values of all referenced variables. This makes them fast and predictable.
5

Escaping Braces

To include a literal curly brace in an f-string, double it: f"{{value}}" outputs {value} as plain text rather than evaluating it.
✦ KEY TAKEAWAY
Think of an f-string like a fill-in-the-blank worksheet. The f prefix tells Python "this worksheet has blanks," the curly braces {} are the blanks, and Python fills them in with the current value of whatever expression you wrote. A format specifier after a colon is like telling Python "write your answer in blue ink, two decimal places, right-aligned"—it controls how the answer looks, not what the answer is.

Visual Explanation — Anatomy of an f-String

The diagram shows each component of the f-string f"Total: {price * qty:>10,.2f} dollars". The f-prefix activates evaluation. The expression is computed at runtime, and the format specifier controls width, alignment, thousand separators, and decimal precision.

Notice how everything lives in a single readable string. The f-prefix tells Python to scan for {} pairs. Each pair contains an expression (the what) and optionally a format specifier after a colon (the how). Without a format specifier, Python simply calls str() on the expression result and drops it in. With a format specifier, you gain fine-grained control over the final appearance.

How f-Strings Work Under the Hood

When Python encounters an f-string, the interpreter does not store it as a template for later. Instead, it treats each {expression} as code that runs immediately. Internally, Python translates the f-string into a series of string concatenations and calls to the built-in format() function. This means f"{x:.2f}" is essentially equivalent to format(x, '.2f'), but wrapped in a much more readable package.

The Format Specifier Mini-Language

The format specifier follows a consistent pattern. While it is not a traditional mathematical formula, it has a structured syntax you should learn to read:

FORMAT SPECIFIER SYNTAX
{expression : fill align sign # 0 width grouping .precision type}
fill = character to pad with (default space) · align = < (left), > (right), ^ (center) · width = minimum number of characters · .precision = digits after decimal · type = d (int), f (float), s (string), e (scientific), % (percent), etc.

Not every field is required—you only include the parts you need. For example, {score:.1f} only specifies precision and type, while {name:*^20} specifies fill, alignment, and width. Let's look at the most common type codes:

COMMON TYPE CODES
d → integer | f → fixed-point float | e → scientific notation | % → percentage | s → string
Example: f"{0.85:.0%}" outputs 85% — Python multiplies by 100 and appends the % sign automatically.
💡 Quick Tip
The equals sign debug shortcut f"{variable=}" prints both the variable name and its value. For example, f"{score=}" outputs score=95. This is incredibly handy for debugging!

Detailed Breakdown of Format Specifiers

Now that you understand the general syntax, let's explore the most commonly used format specifiers in detail. The diagram below shows a quick-reference chart, and the table that follows provides concrete examples you can try in your own Python interpreter.

This reference chart groups format specifiers into four categories: alignment, numeric types, width and precision, and grouping and sign. The bottom panel shows a composite example combining multiple specifiers in one f-string.
Common format specifier examples with their outputs
CodeExpressionOutputWhat It Does
.2ff"{3.14159:.2f}"3.14Rounds float to 2 decimal places
05df"{42:05d}"00042Zero-pads integer to 5 digits
,df"{1000000:,d}"1,000,000Adds comma as thousands separator
.1%f"{0.856:.1%}"85.6%Multiplies by 100, appends %, 1 decimal
^20f"{'Python':^20}" Python Centers text in 20-character field
*>10f"{42:*>10}"********42Right-aligns, fills with asterisks
.3ef"{299792458:.3e}"2.998e+08Scientific notation, 3 decimal places

Worked Example — Building a Receipt

Let's put everything together by building a neatly formatted receipt. We have three items with names, quantities, and prices, and we want to display them in aligned columns with a total at the bottom.

Creating a Formatted Receipt with f-Strings
1
Step 1 — Define the DataWe start by storing our items as a list of tuples. Each tuple contains the item name (string), quantity (integer), and unit price (float). items = [("Notebook", 3, 4.99), ("Pen", 12, 1.50), ("Backpack", 1, 29.95)]
Data stored in items list of tuples.
2
Step 2 — Print the Header RowWe use an f-string with width specifiers to create aligned column headers. The <15 left-aligns in a 15-character field, >5 right-aligns in 5 characters, and >10 right-aligns in 10 characters. print(f"{'Item':<15}{'Qty':>5}{'Price':>10}{'Subtotal':>12}")
Item Qty Price Subtotal
3
Step 3 — Print Each Item RowWe loop through the items and use format specifiers to align each field. Notice .2f ensures prices always show two decimal places, and we compute the subtotal inline. for name, qty, price in items: print(f"{name:<15}{qty:>5}{price:>10.2f}{qty * price:>12.2f}")
Notebook 3 4.99 14.97 Pen 12 1.50 18.00 Backpack 1 29.95 29.95
4
Step 4 — Print a Separator and TotalWe calculate the grand total using a generator expression inside sum(), then print a dashed line and the total, right-aligned to match the Subtotal column. total = sum(qty * price for name, qty, price in items) print(f"{'-'*42}") print(f"{'TOTAL':>32}{total:>10.2f}")
------------------------------------------ TOTAL 62.92
5
Step 5 — Verify the Complete OutputPutting it all together, the output is a clean, column-aligned receipt. Every number is formatted to two decimal places, columns are consistent, and the total is easy to read. All of this was accomplished using only f-strings—no external libraries needed.
Final total: $62.92 — perfectly formatted with aligned columns.

f-Strings vs. Other String Formatting Methods

Python still supports the older formatting methods, so you'll encounter them in existing code and online tutorials. Understanding the differences helps you choose the right tool and read legacy code confidently.

Comparison of Python's three string formatting methods
Feature%-Formattingstr.format()f-Strings
Syntax"%s is %d" % (a, b)"{} is {}".format(a,b)f"{a} is {b}"
ReadabilityLow — values separated from placeholdersMedium — positional or named slotsHigh — expressions inline
Expressions?No — only variablesLimited — via method callsYes — any valid expression
SpeedModerateModerateFastest (compiled at parse time)
Python VersionAll versions2.6+3.6+
Best Use CaseLegacy code maintenanceReusable templatesMost everyday formatting
🔑 WHEN TO USE WHAT
Use f-strings as your default for nearly all string formatting—they're the fastest, most readable, and most Pythonic option. Use str.format() when you need to store a template string and fill it in later (since f-strings evaluate immediately). Avoid %-formatting in new code unless you're maintaining an older project.

Connection to Advanced String Techniques

f-strings are your gateway to more advanced string handling in Python. As you progress, you'll encounter scenarios where formatting gets more complex—logging frameworks, internationalization (i18n), and template engines. Understanding how f-strings relate to these tools will set you up for success in larger projects.

How f-string skills connect to advanced Python topics
Conceptf-String SkillAdvanced Extension
Custom __format__Using format specifiers like :.2fDefine custom format behavior for your own classes by writing a __format__ method
LoggingBuilding readable output stringsThe logging module uses %-style by default but can use f-strings with lazy evaluation
Template StringsInline expression evaluationFor user-supplied templates, string.Template is safer because it doesn't execute arbitrary code
InternationalizationNumber and date formattingLibraries like babel handle locale-specific formatting (commas vs. periods for decimals)
⚠️ Security Note
Because f-strings execute arbitrary Python expressions, never construct f-strings from user input. A malicious user could inject code that runs on your system. If you need to format strings using data from untrusted sources, use str.format_map() or string.Template instead.

As you move into web development, data science, or software engineering, you'll find that the format specifier mini-language you learned here applies far beyond f-strings. The same codes work with str.format(), the format() built-in function, and even pandas DataFrames. Mastering these specifiers now gives you a skill that transfers across the Python ecosystem.

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain in your own words why f"{name}" is different from "{name}" (without the f prefix). What happens if you forget the f?
PROBLEM 2 — BASIC CALCULATION
Given temperature = 98.6473, write an f-string that prints: Body temp: 98.65°F. The temperature should be rounded to exactly two decimal places.
PROBLEM 3 — INTERMEDIATE
You have product = "Widget" and price = 9.5. Write a single f-string that produces: Widget..........$9.50 (the product name left-aligned and the price right-aligned, with dots filling the space between, total width 22 characters).
PROBLEM 4 — APPLIED
You're building a grade report. Given students = [("Ava", 92.345), ("Ben", 87.1), ("Cara", 100.0)], write a loop using f-strings that prints each student's name left-aligned in 12 characters, their score as a percentage with one decimal place, and their letter grade (A for ≥90, B for ≥80, otherwise C). Example output: Ava 92.3% A
PROBLEM 5 — CRITICAL THINKING
A classmate writes: user_template = input("Enter template: ") and then uses eval(f"f'{user_template}')" to let users create custom messages. Explain why this is dangerous, and propose a safer alternative that still lets users insert variables like {name} and {score} into their template.

Summary

f-strings (formatted string literals) are Python's preferred way to embed expressions directly inside strings. By placing the f-prefix before the opening quote, any code inside {} is evaluated at runtime. Adding a colon and format specifier after the expression gives you precise control over output: decimal precision with .2f, alignment and padding with < > ^, thousands separators with commas, and percentage conversion with %.

Compared to older methods like %-formatting and str.format(), f-strings are faster, more readable, and more concise. They support any valid Python expression inside the braces—including arithmetic, function calls, and conditionals. Remember: use f-strings as your default, but switch to str.format() for reusable templates and string.Template for untrusted input to stay safe and flexible.

Varsity Tutors • Python • f-Strings & Formatting