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.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 7 figures

SECTION 1

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.

SECTION 2

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.

SECTION 3

Visual Explanation — Anatomy of an f-String

Anatomy of an f-String Expressionf"Total: {price * qty:>10,.2f} dollars"f-prefixActivates formatting{ }Expression delimitersExpressionAny valid Python code(evaluated at runtime): (colon)Separates expressionfrom format specFormat Specifier> = right-align10 = width 10 chars, = thousands separator.2f = 2 decimal placesIf price=19.99, qty=3 →"Total: 59.97 dollars"
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.

SECTION 4

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−pointfloat∣e→scientificnotation∣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!

SECTION 5

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.

Format Specifier Quick ReferenceALIGNMENT< left-align> right-align^ center= pad after signNUMERIC TYPESd integerf fixed floate scientific% percentageWIDTH & PRECISION10 min 10 chars.2f 2 decimals.5s truncate to 508d zero-pad to 8GROUPING & SIGN, → 1,000,000 (comma separator)_ → 1_000_000 (underscore sep)+ → always show + or −space→ space before positiveLIVE EXAMPLESf"{42:05d}" → 00042f"{3.14159:.2f}" → 3.14f"{'hi':*^10}" → ****hi****f"{0.856:.1%}" → 85.6%Putting It TogetherGiven: name = "Ada", score = 97.5, total = 2500000f"{name:<10} scored {score:06.2f} out of {total:,.0f}"→ "Ada scored 097.50 out of 2,500,000"
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.
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

Common format specifier examples with their outputs

SECTION 6

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

    Step 1 — Define the Data

    We 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. 2

    Step 2 — Print the Header Row

    We 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. 3

    Step 3 — Print Each Item Row

    We 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. 4

    Step 4 — Print a Separator and Total

    We 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. 5

    Step 5 — Verify the Complete Output

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

SECTION 7

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.

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

Comparison of Python's three string formatting methods

🔑

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.

SECTION 8

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.

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)

How f-string skills connect to advanced Python topics

⚠

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.

SECTION 9

Practice Problems

Question 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?
Show answer
Without the f prefix, Python treats the string as a plain string literal. The curly braces and the word name become literal characters in the string, so you'd get the output {name} instead of the variable's value. The f prefix activates the expression evaluation engine, telling Python to look inside the braces for code to execute. This is a common beginner mistake—always double-check that the f is present before the opening quote.

Question 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.
Show answer
The solution is f"Body temp: {temperature:.2f}°F". The format specifier :.2f tells Python to format the number as a fixed-point float with exactly 2 decimal places. The f type code means "fixed-point," and .2 means two digits after the decimal point. Python rounds 98.6473 to 98.65 automatically. The degree symbol ° is placed as literal text outside the braces.

Question 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).
Show answer
One clean approach is: f"{product:.<15}${price:>6.2f}". Here, .<15 left-aligns the product name in a 15-character field and fills empty spaces with dots. The $ is literal text. Then >6.2f right-aligns the price in a 6-character field with 2 decimal places. The fill character (the dot before <) must come immediately before the alignment character. This technique is great for creating formatted menus, invoices, or table-of-contents displays.

Question 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
Show answer
The solution uses an inline conditional expression inside the f-string: for name, score in students: grade = 'A' if score >= 90 else 'B' if score >= 80 else 'C' print(f"{name:<12}{score:>5.1f}% {grade}") The <12 left-aligns the name in 12 characters. The >5.1f right-aligns the score in 5 characters with 1 decimal. You could also put the conditional directly inside the braces: {'A' if score >= 90 else 'B' if score >= 80 else 'C'}, but assigning it to a variable first is more readable. This example demonstrates how f-strings work seamlessly inside loops for real-world data formatting.

Question 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.
Show answer
This is extremely dangerous because eval() executes arbitrary Python code. A malicious user could enter something like {__import__('os').system('rm -rf /')} inside the template, and it would run as real Python. The safer alternative is to use str.format_map() with a controlled dictionary: user_template.format_map({'name': name, 'score': score}). This only substitutes the keys you explicitly provide and does not evaluate Python expressions. Another option is string.Template(user_template).safe_substitute(name=name, score=score), which is even more restrictive. The key lesson: f-strings are powerful because they execute code, and that same power makes them unsafe for untrusted input.

SUMMARY

Summary

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