Technology Strings | Python Tutorial

Learn with diagrams, code, systems and practical examples.

</>
TutorialsPythonStrings
Core Python · Chapter 6

Strings

Create, combine, inspect, and format text.

Why this matters

Applications constantly work with names, messages, file paths, API data, and user-facing text.

Start with the idea

A string is a sequence of characters. You can create strings with quotes, access characters by position, call helpful methods, and format values into readable messages.

IdeaUnderstand the purpose
CodeRun the smallest example
PracticeChange it yourself
SubjectVision learning pattern: understand the idea before memorizing syntax.

Small working example

Run this example first. Do not change several things at once; confirm the basic behavior, then experiment.

Python
name = "Maya"
items = 3
message = f"{name} has {items} items"
print(message)
print(name.lower())
Expected output
Maya has 3 items
maya
What to notice

The example is intentionally small. Focus on the chapter’s main idea before adding extra syntax or framework code.

Common beginner mistake

Mistake

Building long messages with many + operators and forgetting spaces or type conversions.

Better approach

Use f-strings for most readable value interpolation.

Quick recap

  • Strings hold text.
  • String methods return useful transformed values.
  • F-strings make dynamic text easy to read.

Try it yourself

These are deliberately small. If you can complete them without copying the example, you are ready to continue.

  1. Format a sentence with a product and price.
  2. Use strip(), lower(), or upper() on sample input.

Prepared deep chapter · Core Python

Strings turn raw values into useful text

A program often needs to work with text before it does anything complicated. A customer name, a file path, a search term, a status message, and a line from a log file are all text. In Python, text is represented by the str type. A string is a sequence of Unicode characters, which means it can hold ordinary letters, digits, punctuation, spaces, and text from many writing systems.

The beginner goal is not to memorize dozens of string methods. It is to learn one reliable workflow: identify the text you have, decide what you need to read or change, produce a new string, then inspect the result. Strings are immutable, so operations that look like changes actually return new string values.

From raw text to a checked message A raw user value is cleaned, transformed, and formatted into a final message. Each step produces a new string and the final value is checked before use. raw text" maya " clean + normalizestrip().title() formatted result"Hello, Maya!" raw text → transform → new string → verify output
Think of string work as a pipeline. Read the current value, transform only what the task requires, then inspect the new result.

Start with the smallest useful string

A string literal is text written directly in your program. Python accepts single or double quotes. Pick the style that makes the text easiest to read.

customer = "Maya"
status = 'ready'

print(customer)
print(status)
Expected output
Maya
ready

The quotes mark where the string begins and ends; they are not normally part of the value itself. A value such as "1975" is still a string because it is quoted, even though its characters look like a number.

Beginner checkpoint

If you need arithmetic, use a numeric type. If you need text such as an order code, postcode, phone-number label, or identifier, a string may be the better model even when it contains digits.

Choose quotes that make the text readable

You can put a single quote inside a double-quoted string, or a double quote inside a single-quoted string. When the same quote character must appear inside the string, escape it with a backslash.

message = "Maya's order is ready."
quoted = 'The status is "ready".'
escaped = 'It\'s ready.'

print(message)
print(quoted)
print(escaped)
Expected output
Maya's order is ready.
The status is "ready".
It's ready.

Escape sequences can also represent special characters. For example, \n creates a newline and \t represents a tab.

receipt = "Item: Notebook\nQty: 2"
print(receipt)
Expected output
Item: Notebook
Qty: 2
Good to know: raw strings

A raw string begins with r, for example r"C:\Users\Maya". Most backslashes are treated literally, which can be useful for Windows-style paths or regular-expression patterns. Raw strings still have syntax rules, so they are not a universal “ignore every backslash” mode.

Build messages without losing the meaning

You can combine strings with +, but repeated concatenation quickly becomes hard to read when values are mixed into a sentence. For user-facing messages, an f-string usually makes the relationship between the sentence and its values clearer.

name = "Maya"
items = 3

message = f"Hello, {name}. You have {items} items."
print(message)
Expected output
Hello, Maya. You have 3 items.

The expressions inside the braces are evaluated and converted for display. Keep f-string expressions small enough that the sentence remains easy to scan. If a calculation is complicated, compute it first, give it a meaningful name, then format the result.

Good
subtotal = price * quantity
print(f"Subtotal: ${subtotal:.2f}")

The calculation has a name and the formatting step is easy to verify.

Harder to read
print(f"Subtotal: ${price * quantity:.2f}")

This is valid, but large expressions inside a message can hide business logic.

Read parts of a string with indexes and slices

Strings are sequences. Each character has a position called an index. Python indexes start at zero, so the first character is position 0. Negative indexes count from the end.

code = "SV-2048"

print(code[0])
print(code[-1])
print(code[:2])
print(code[3:])
Expected output
S
8
SV
2048

A slice uses a start position and an end position, and the end position is excluded. That rule is useful because text[:n] contains at most the first n characters.

Predict before running

For word = "python", predict word[1:4]. The answer is "yth": indexes 1, 2, and 3 are included; index 4 is the stopping point.

Strings are immutable: operations return new strings

Immutable means the string object cannot have one of its characters replaced in place. This fails:

name = "maya"
name[0] = "M"
Result
TypeError: 'str' object does not support item assignment

Create a new string instead. Most string methods follow the same pattern: they return a new value and leave the original string unchanged.

name = "maya"
formatted = name.title()

print(name)
print(formatted)
Expected output
maya
Maya

This behavior matters when you clean input. Calling email.strip() without storing or using the returned value does not rewrite email.

Learn a small method toolkit first

Python strings have many methods, but beginners can solve a surprising number of tasks with a small set. Learn them by the problem they solve rather than by alphabetical order.

Small string-method toolkit
NeedMethodExample result
Remove surrounding whitespacestrip()" Maya ".strip()"Maya"
Normalize case for comparisonlower() / casefold()"READY".lower()"ready"
Replace known textreplace()"SV-2048".replace("SV", "APP")
Break text into piecessplit()"red,blue".split(",")
Join pieces into textjoin()" / ".join(["api", "users"])
raw_tags = " cloud,python, beginner "
tags = [tag.strip() for tag in raw_tags.split(",")]
summary = " | ".join(tags)
print(summary)
Expected output
cloud | python | beginner

You do not need to memorize the whole method catalog. Start with the transformation you need, inspect the returned value, and add methods only when a real task requires them.

Normalize text deliberately before comparing it

String comparisons are exact unless you transform the values first. That means "READY" == "ready" is False. If the business rule says case should not matter, make that rule explicit.

entered = "  READY "
normalized = entered.strip().casefold()

if normalized == "ready":
    print("Continue")
Expected output
Continue

Do not automatically lowercase text just because you can. Usernames, identifiers, product codes, file paths, and external systems can have their own case rules. Normalize only when the domain says values should be compared that way.

Five predictable beginner mistakes

Forgetting that strings are immutable

text.upper() returns a new string. Assign it or use it directly.

Mixing numbers and text with +

"Total: " + 5 raises TypeError. Convert deliberately or use an f-string.

Off-by-one slices

The slice end is excluded. Predict positions before slicing user-facing identifiers.

Using split() without checking the input contract

Real text may contain missing fields, extra separators, or unexpected whitespace. Validate structure before indexing pieces.

Normalizing identifiers blindly

Case folding is useful only when the domain says case should not carry meaning.

Build a small shipping label step by step

A checkout form collects a customer name and a city with accidental surrounding spaces. We want a readable label and a normalized city for comparison.

raw_name = "  maya chen "
raw_city = " Seattle "
order_id = 2048

name = raw_name.strip().title()
city = raw_city.strip()
city_key = city.casefold()
label = f"Order #{order_id} — {name} — {city}"

print(label)
print(city_key)
Expected output
Order #2048 — Maya Chen — Seattle
seattle

Each transformation has one reason: strip() removes accidental outer whitespace, title() prepares a display name for this example, casefold() creates a separate comparison key, and the f-string builds the final label. Keeping display text and comparison text as separate variables makes the intent easier to inspect.

Deeper detail: do not over-normalize human names

Real names do not all follow the same capitalization rules. The title() call above is a teaching example, not a universal name-cleaning policy. In production software, preserve user-provided names unless your product has a carefully defined normalization requirement.

Practice before moving on

  1. Given raw = " python ", create a cleaned value without changing the original variable. Print both values.
  2. Predict the result of "SubjectVision"[0:7] and "SubjectVision"[-6:], then run the code.
  3. Create a product message using an f-string with a product name, quantity, and price formatted to two decimal places.
  4. Take "red, green,blue", split it into pieces, remove surrounding whitespace from each piece, and join the cleaned values with " | ".
  5. Write a small input-normalization example where display text preserves the user's capitalization but a separate comparison key uses casefold().

Quick recap

  • Python text values use the immutable str type.
  • Quotes and escape sequences control how string literals are written.
  • Indexes and slices read parts of a string without mutating it.
  • String methods return new strings; store the result when you need it later.
  • F-strings make readable messages when expressions stay small and clear.

Next chapter: Booleans and Comparisons — where values become yes/no decisions for control flow.

Official references used in this chapter

Trace practice · predict before you run

Turn a messy support-ticket line into trustworthy fields

Real programs rarely receive perfectly formatted text. A support tool might receive one line such as " INC-2048 | Payment failed | HIGH ". Before writing a clever parser, trace a few small transformations and inspect what each one produces.

The goal is not to memorize strip() or split(). The goal is to see the state change: raw text → cleaned text → pieces → validated fields → comparison key.

Predict first

Before revealing the trace, decide which spaces should disappear and which capitalization should remain visible to the user. Should the summary Payment failed be lowercased just because the priority is compared case-insensitively?

  1. 1 · Keep the raw valueraw = " INC-2048 | Payment failed | HIGH "

    The original input is useful evidence when debugging. Do not destroy it before you know what transformations are required.

  2. 2 · Remove outer whitespacecleaned = raw.strip()

    Now the leading and trailing spaces are gone, but the spaces around each separator still exist.

  3. 3 · Split into piecesparts = cleaned.split("|")

    The result is a list of three strings. Each field still needs its own surrounding whitespace removed.

  4. 4 · Clean each fieldfields = [part.strip() for part in parts]

    The visible summary keeps its capitalization. Cleaning does not mean normalizing everything indiscriminately.

  5. 5 · Validate the shapeif len(fields) != 3: ...

    Check the structure before unpacking. Otherwise malformed input can raise ValueError at an unexpected place.

  6. 6 · Normalize only the comparison fieldticket_id, summary, priority = fields priority_key = priority.casefold()

    The display value remains HIGH; the separate comparison key becomes high.

Small working version

raw = "  INC-2048 | Payment failed | HIGH  "
cleaned = raw.strip()
fields = [part.strip() for part in cleaned.split("|")]

if len(fields) != 3:
    print("Invalid ticket format")
else:
    ticket_id, summary, priority = fields
    priority_key = priority.casefold()
    print(ticket_id)
    print(summary)
    print(priority_key)
Expected output
INC-2048
Payment failed
high
Predictable failure: missing separators

If the input is "INC-2048 Payment failed HIGH", splitting on | produces only one field. Checking len(fields) before unpacking turns a confusing exception into an explicit validation decision.

Why this trace matters

String processing is easier to debug when each transformation has one job. strip() removes unwanted outer whitespace. split() changes one string into pieces. A length check validates the expected shape. casefold() creates a comparison key only where the domain says case should not matter.

This separation also protects user-facing text. The support summary is not forced to lowercase merely because the priority is compared case-insensitively. That is the same principle used in the chapter's shipping-label example: preserve display data when possible and create a separate normalized value for comparison.

Mini-project · one new idea at a time

Keep a note intact when the note contains your separator

Suppose an order-import line has three logical fields: an order ID, a customer label, and a free-text delivery note. The note itself might contain a | character. A plain split("|") would split every separator and create too many pieces.

Predict before running

For " ORD-1042 | Priority Customer | leave at side | gate ", how many pieces would plain split("|") produce? It produces four. But the data contract says there should be only three fields.

Good to know: split(separator, maxsplit) can limit how many splits Python performs. With maxsplit=2, Python splits only the first two separators, so everything after the second separator stays in the final field.

raw = " ORD-1042 | Priority Customer | leave at side | gate "
cleaned = raw.strip()
parts = [part.strip() for part in cleaned.split("|", 2)]

if len(parts) != 3:
    print("Invalid order format")
else:
    order_id, customer_label, note = parts
    comparison_key = customer_label.casefold()

    print(order_id)
    print(customer_label)
    print(comparison_key)
    print(note)
Expected output
ORD-1042
Priority Customer
priority customer
leave at side | gate

Notice the two separate jobs: customer_label preserves the display text, while comparison_key is normalized only for comparison. The note is cleaned around the edges but otherwise preserved. That keeps a formatting decision from silently destroying user-entered information.

Failure case: still validate the shape

maxsplit=2 does not guarantee the separators exist. If the line contains only one separator, the result still has fewer than three fields. Keep the length check before unpacking.

Try two variations

  1. Change the customer label to priority CUSTOMER. Predict which printed value changes and which normalized key stays equivalent.
  2. Remove one separator from the raw line. Make the program report Invalid order format without raising an unpacking exception.

Try two changes yourself

  1. Add a fourth field for an assignee. Update the validation check before changing the unpacking statement.
  2. Feed the parser " INC-2050 | Login issue | urgent ". Predict the display values and the normalized priority before running it.

Reasoning lab · predict before you reveal

Eight small string decisions that prevent bigger bugs

Each prompt asks you to predict what Python will do before reading the explanation. Focus on the rule behind the result rather than memorizing one line of code.

1Does strip() change the original string?
raw = "  Maya  "
raw.strip()
print(raw)

Prediction: the spaces are still there. Strings are immutable, so strip() returns a new string. Store or use that returned value when you want the cleaned text.

2What happens when text and a number are added with +?
count = 3
print("Items: " + count)

Prediction: Python raises TypeError. Use an f-string such as f"Items: {count}" when the goal is readable output.

3Where does a slice stop?
word = "python"
print(word[1:4])

Prediction: yth. The start index is included and the stop index is excluded.

4Why can unpacking after split() fail?
ticket = "INC-2048 Payment failed HIGH"
id_, summary, priority = ticket.split("|")

Prediction: the split returns one piece, so unpacking three variables raises ValueError. Validate the number of fields before unpacking external text.

5Which object owns join()?
parts = ["api", "users", "42"]
print("/".join(parts))

Prediction: api/users/42. The separator string owns join(); the list supplies the pieces to combine.

6Does replace() edit text in place?
code = "SV-2048"
updated = code.replace("SV", "APP")
print(code)
print(updated)

Prediction: the original remains SV-2048; the new value is APP-2048. This is another consequence of string immutability.

7Should every user-visible value be case-folded?
display_priority = "HIGH"
priority_key = display_priority.casefold()

Prediction: keep both when the interface should display HIGH but comparisons should ignore case. Normalize only the field whose domain rules require normalization.

8Why can title() be risky for real names?
name = "o'connor"
print(name.title())

Prediction: a transformation may produce output that looks plausible, but capitalization rules for real names are not universal. Preserve user-provided names unless the product has a defined normalization policy.

Bookmark this · choose by intent

Which string tool should I reach for?

Start from the job you need to do, not from a method name you happen to remember. The safe pattern is: choose the operation, keep the original value when it still matters, then verify the result.

Intent-first string reference
Your intentStart withGood small exampleCommon misuse to avoid
Remove accidental whitespace at the edgesstrip()clean = raw.strip()Calling raw.strip() and then continuing to use raw as though it changed.
Compare human-entered text without case differencescasefold() on a separate keypriority_key = priority.casefold()Replacing the display value when the interface should preserve the user's original capitalization.
Split a delimited record but preserve free text at the endsplit(separator, maxsplit)
fields = row.split("|", 2)
if len(fields) != 3:
    raise ValueError("Expected order_id | name | note")
order_id, name, note = fields
Unpacking the split result before confirming that external input has the expected number of fields.
Combine known pieces with a separatorseparator.join(parts)path = "/".join(parts)Calling parts.join("/"); the separator string owns join().
Build readable output from text plus valuesan f-stringlabel = f"Order {order_id} — {name}"Hiding large calculations inside the braces instead of naming the calculation first.
Replace one known substring according to a defined rulereplace()new_code = code.replace("SV-", "APP-", 1)Using replacement as a parser when the input can have several shapes or ambiguous occurrences.
Read a fixed position or range from a stable formatindexing or slicingprefix = code[:2]Using magic positions for free-form user text whose structure is not guaranteed.

Good to know · Text and bytes

A string is text; bytes are an encoded representation of that text.

Most beginner Python programs can stay with str. You need to think about bytes when text crosses a boundary such as a file format, a network protocol, or a library that expects encoded data. An encoding is a rule for turning text characters into bytes. UTF-8 is a widely used encoding that can represent Unicode text.

Start with the smallest useful round trip

message = "café"
encoded = message.encode("utf-8")
decoded = encoded.decode("utf-8")

print(message)
print(encoded)
print(decoded)

Expected result: the first and third lines are the same human-readable text. The middle line is a bytes value, which Python displays with a leading b. The exact byte escapes depend on the characters in the text.

Predict before running

Will len("é") always equal len("é".encode("utf-8"))? No. The first counts Unicode characters in the Python string; the second counts encoded bytes. Different encodings and characters can use different byte counts.

Common mistake

Do not mix str and bytes as if they were interchangeable. If an API expects bytes, encode deliberately. If you receive bytes and need text, decode with the encoding that actually produced those bytes.

Deeper detail: decoding bytes with the wrong encoding can produce incorrect text or a UnicodeDecodeError. Do not fix that by guessing encodings blindly; use the file, protocol, or API contract that defines the encoding.

Two small practice tasks

  1. Encode "hello" as UTF-8, decode it again, and verify that the round trip preserves the original text.
  2. Compare the character length and UTF-8 byte length of "café". Explain why they can differ without saying that one result is “wrong.”