Strings: Intermediate

SendGrid delivers over 100 billion emails per year for companies like Uber, Spotify, and Airbnb, and the engine behind every one of those personalized messages is Python string formatting. A single email template becomes millions of individually addressed messages by merging customer names, order IDs, and dynamic content using f-strings and format operations. The string formatting, join, replace, find, and regex basics you learn in this lesson are exactly how SendGrid transforms a generic design into a message that feels written for you specifically.

String Indexing

Daily Life
Interviews
Every character in a string has a position number called an index. Python uses zero-based indexing, meaning the first character is at position 0, not position 1.
1text = "Python"
2
3print(text[0])
4print(text[1])
5print(text[5])
>>>Output
P
y
n
This prints "P", "y", and "n". The characters are at positions 0, 1, and 5 respectively.

Zero-Based Indexing

Consider the string "Python". Here's how each character is indexed:
Character Positions in "Python"
  • P = index 0 (first character)
  • y = index 1
  • t = index 2
  • h = index 3
  • o = index 4
  • n = index 5 (last character)
The last character's index is always the string length minus 1. For a 6-character string, the last index is 5.

Negative Indexing

Python has a powerful feature: negative indices count from the end of the string. -1 is the last character, -2 is second to last, and so on.
1text = "Python"
2
3print(text[-1])
4print(text[-2])
5print(text[-6])
>>>Output
n
o
P
This prints "n", "o", and "P". Negative indexing makes it easy to access characters from the end without knowing the exact length.
Positive Index
  • Counts from start
  • text[0] = first char
  • text[5] = 6th char
Negative Index
  • Counts from end
  • text[-1] = last char
  • text[-6] = 6th from end
TIP
Use text[-1] to get the last character instead of text[len(text)-1]. It's cleaner and more Pythonic.
Python Quiz

> A string "Python" is accessed using both positive and negative indexing. Pick the correct negative index to get the last character, and the correct positive index to get the first character.

text = "Python"
first = text[___]
last = text[___]
print(first)
print(last)
1
-1
0
-6
5

Index Out of Range

Accessing an index that doesn't exist raises an error:
1text = "Python"
2print(text[10])

This raises IndexError: string index out of range. Always check the string length before accessing by index if the position might be invalid.

String Slicing

Daily Life
Interviews
Slicing extracts a portion of a string using the syntax [start:end]. The slice includes the start index but excludes the end index.
1text = "Python Programming"
2
3print(text[0:6])
4print(text[7:18])
>>>Output
Python
Programming

The first slice [0:6] returns "Python" (characters 0 through 5). The second [7:18] returns "Programming" (characters 7 through 17).

Slice Defaults

You can omit the start or end index. Omitting start defaults to 0. Omitting end defaults to the string length.

1text = "Python Programming"
2
3print(text[:6])
4print(text[7:])
5print(text[:])
>>>Output
Python
Programming
Python Programming

text[:6] gives "Python" (from start to index 6). text[7:] gives "Programming" (from index 7 to end). text[:] creates a copy of the entire string.

Negative Slice Indices

Negative indices work in slices too, making it easy to extract from the end:
1filename = "document.pdf"
2
3extension = filename[-3:]
4name = filename[:-4]
5
6print(extension)
7print(name)
>>>Output
pdf
document

This extracts "pdf" using filename[-3:] (last 3 characters) and "document" using filename[:-4] (everything except last 4 characters including the dot).

Slice Step

A third parameter specifies the step (how many characters to skip):
1text = "Python"
2
3print(text[::2])
4print(text[1::2])
5print(text[::-1])
>>>Output
Pto
yhn
nohtyP

text[::2] gives "Pto" (every other character). text[1::2] gives "yhn" (every other starting from index 1). text[::-1] gives "nohtyP" (reversed string).

Searching in Strings

Daily Life
Interviews
Python provides several ways to search for substrings within a string. Each method has specific use cases.

The in Operator

The in operator checks if a substring exists within a string. It returns True or False.

1email = "user@example.com"
2
3print("@" in email)
4print("gmail" in email)
>>>Output
True
False
The first returns True (@ is in the email). The second returns False (gmail is not).

find() and index()

.find() returns the position of the first occurrence of a substring, or -1 if not found. .index() does the same but raises an error if not found.

1email = "user@example.com"
2
3at_pos = email.find("@")
4print(at_pos)
5
6dot_pos = email.find(".", at_pos)
7print(dot_pos)
>>>Output
4
12
This finds @ at position 4, then searches for . starting from position 4, finding it at position 12.
find()
  • Returns -1 if not found
  • Safe for conditional checks
  • Use when substring might not exist
index()
  • Raises ValueError if not found
  • Use when substring must exist
  • Crashes if assumption wrong

The count() Method

.count() returns the number of non-overlapping occurrences of a substring:

1text = "banana"
2
3print(text.count("a"))
4print(text.count("na"))
5print(text.count("x"))
>>>Output
3
2
0
This returns 3 (three a's), 2 (two "na" occurrences), and 0 (no x's).

startswith() and endswith()

These methods check if a string begins or ends with a specific substring:
1filename = "report.pdf"
2
3print(filename.endswith(".pdf"))
4print(filename.endswith(".txt"))
5print(filename.startswith("report"))
>>>Output
True
False
True
These are commonly used for file type validation, URL checking, and prefix/suffix matching.
TIP
Both methods accept tuples for multiple options: filename.endswith((".pdf", ".doc", ".txt")) returns True if the filename ends with any of those extensions.
Python Quiz

> You need to validate a filename by checking its extension and counting how many dots it contains. Pick the correct method to test if the string ends with ".csv", and the correct method to count occurrences of a character.

name = "data.backup.csv"
print(name.___(".csv"))
print(name.___("."))
count
index
endswith
find
len

startswith() and endswith() are cleaner than slicing for prefix and suffix checks. They also accept a tuple of options, so filename.endswith((".pdf", ".docx", ".txt")) checks all three extensions in a single readable call.

count() returns how many non-overlapping times a substring appears in a string. It is useful for quick frequency checks, such as counting how many dots are in a filename to determine if it has multiple extensions.

TIP
Use find() when you need the position of a substring, and count() when you need the frequency. If you just need to know whether a substring exists at all, the in operator is the most readable choice: if ".csv" in filename:.

String Transformation

Daily Life
Interviews
Python provides methods to modify strings. Remember: strings are immutable, so these methods return new strings rather than modifying the original.

strip(), lstrip(), rstrip()

.strip() removes whitespace from both ends of a string. .lstrip() removes from the left only, .rstrip() from the right only.

1user_input = " hello world "
2
3clean = user_input.strip()
4print(clean)
5print(len(user_input))
6print(len(clean))
>>>Output
hello world
17
11
The stripped string is "hello world" with length 11. The original had length 17 including the spaces.
You can specify which characters to strip:
1price = "$99.99$"
2clean_price = price.strip("$")
3print(clean_price)
>>>Output
99.99
This removes dollar signs from both ends, giving "99.99".

The replace() Method

.replace() substitutes all occurrences of a substring with another:

1text = "I like cats. Cats are great."
2
3new_text = text.replace("cats", "dogs")
4print(new_text)
>>>Output
I like dogs. Cats are great.

This replaces "cats" with "dogs". Note that "Cats" with capital C is not replaced because replace() is case-sensitive.

You can limit the number of replacements:
1text = "one two one two one"
2result = text.replace("one", "1", 2)
3print(result)
>>>Output
1 two 1 two one
This replaces only the first 2 occurrences, giving "1 two 1 two one".

Removing Characters

To remove characters, replace them with an empty string:
1phone = "(555) 123-4567"
2digits = phone.replace("(", "").replace(")", "").replace("-", "").replace(" ", "")
3print(digits)
>>>Output
5551234567

This chains replace() calls to remove all formatting, leaving just "5551234567".

F-Strings

Daily Life
Interviews

F-strings (formatted string literals) are the modern way to embed expressions inside strings. Prefix the string with f and use curly braces {} to insert values.

1name = "Maya"
2age = 28
3
4message = f"My name is {name} and I am {age} years old."
5print(message)
>>>Output
My name is Maya and I am 28 years old.

This outputs: "My name is Maya and I am 28 years old." The variables are automatically converted to strings inside the {} placeholders.

Expressions in F-Strings

You can put any expression inside the curly braces, not just variables:
1name = "Maya"
2price = 49.99
3quantity = 3
4
5total = f"Total: {price * quantity}"
6print(total)
7
8greeting = f"Hello, {name.upper()}!"
9print(greeting)
>>>Output
Total: 149.97
Hello, MAYA!
The expressions are evaluated, then the results are inserted into the string.

Number Formatting

F-strings support format specifications for controlling number display:
1price = 49.5
2percentage = 0.854
3
4print(f"Price: ${price:.2f}")
5print(f"Rate: {percentage:.1%}")
6print(f"Large: {1000000:,}")
>>>Output
Price: $49.50
Rate: 85.4%
Large: 1,000,000

These print: "$49.50" (2 decimal places), "85.4%" (percentage format), and "1,000,000" (comma separators).

Common Format Specifiers
  • :.2f - 2 decimal places for float
  • :.0f - No decimal places (rounded)
  • :,% - Percentage with comma separators
  • :>10 - Right-align in 10 character width
  • :08d - Zero-pad integer to 8 digits
F-strings have largely replaced concatenation as the preferred approach since Python 3.6.
Concatenation
  • "Hi " + name + "!"
  • Must convert numbers
  • Hard to read with many values
F-Strings
  • f"Hi {name}!"
  • Auto-converts types
  • Clear and readable
TIP
F-strings were introduced in Python 3.6 and are now the recommended way to format strings. They are faster and more readable than older methods like % formatting or .format().

String Checking Methods

Python provides methods to check string properties. These all return True or False.
Checking Methods
  • .isalpha() - All characters are letters
  • .isdigit() - All characters are digits
  • .isalnum() - All characters are letters or digits
  • .isspace() - All characters are whitespace
  • .isupper() - All letters are uppercase
  • .islower() - All letters are lowercase
1username = "maya123"
2
3print(username.isalnum())
4print(username.isalpha())
5print(username.isdigit())
6
7pin = "1234"
8print(pin.isdigit())
>>>Output
True
False
False
True
The username is alphanumeric (True), not all letters (False), not all digits (False). The PIN is all digits (True).

Practical Validation

These methods are perfect for input validation:
1def is_valid_username(username):
2 if len(username) < 3:
3 return False
4 if not username[0].isalpha():
5 return False
6 if not username.isalnum():
7 return False
8 return True
9
10print(is_valid_username("maya123"))
11print(is_valid_username("ab"))
12print(is_valid_username("2fast"))
>>>Output
True
False
False
This validates that a username is at least 3 characters, starts with a letter, and contains only letters and numbers. The second fails (too short), the third fails (starts with a digit).
Test your understanding of string slicing by picking the right slice to extract a file extension.
Fill in the Blank

> A file is named "report.pdf" and you need to extract just the extension. Pick a slice notation that reliably grabs the last three characters.

filename = "report.pdf"
ext = filename
print(ext)

Negative slicing with [-n:] reliably extracts the last n characters of any string, regardless of its total length. This makes it more robust than hardcoded index slices like [7:], which break if the string length changes.

String slicing is non-destructive: it always returns a new string and leaves the original unchanged. You can chain multiple slices or combine slicing with method calls in a single expression.
Now practice fixing a common string method bug. The code below uses replace() but has a case sensitivity issue.
Debug Challenge

> This code uses .replace("cats", "dogs") but only catches the lowercase occurrence. The capitalized "Cats" is left unchanged because replace() is case-sensitive.

Only replaces lowercase "cats", outputting "I like Cats and dogs"

Case sensitivity is one of the most common sources of string comparison bugs. Always normalize user input to a consistent case before comparing or replacing. Calling .lower() before .replace() ensures both "cats" and "Cats" and "CATS" are all matched.

String methods form a toolkit that you will use in almost every Python project. Here is a quick reference of the most essential ones covered in this lesson.
Indexing and slicing
Indexing and slicing
Access characters by position and extract substrings with [start:end:step].
Search methods
Search methods
Use in, find(), count(), startswith(), and endswith() to locate text.
Transform methods
Transform methods
strip(), replace(), and case methods to clean and modify strings.
Validation methods
Validation methods
isdigit(), isalpha(), isalnum() to verify string content.
PUTTING IT ALL TOGETHER

> You are a data engineer at FedEx parsing carrier tracking numbers and address strings from raw API response bodies to extract structured fields for the shipment tracking database.

str[0] indexing pulls the carrier prefix character from the start of each tracking number to route it to the correct validation handler.
str[start:end] slicing extracts the fixed-length zone code and sequence digits embedded at known offsets within each tracking number format.
Searching methods like find() and in locate the delimiter between address components before splitting compound fields into columns.
f"..." f-strings format the parsed city, state, and zip components into the canonical address string the database schema requires.
KEY TAKEAWAYS
String indexing starts at 0; use text[0] for first character
Negative indices count from end: text[-1] is the last character
Slicing uses [start:end] - end index is excluded
[::-1] reverses a string efficiently
in operator checks if substring exists; .find() returns position
.strip() removes whitespace; .replace() substitutes substrings
F-strings f"{var}" are the modern way to format strings
Use .isdigit(), .isalpha() etc. for input validation

Slice, index, and transform text

Category
Python
Difficulty
intermediate
Duration
33 minutes
Challenges
0 hands-on challenges

Topics covered: String Indexing, String Slicing, Searching in Strings, String Transformation, F-Strings

Lesson Sections

  1. String Indexing (concepts: pyStringIndex)

    Every character in a string has a position number called an index. Python uses zero-based indexing, meaning the first character is at position 0, not position 1. This prints "P", "y", and "n". The characters are at positions 0, 1, and 5 respectively. Zero-Based Indexing Consider the string "Python". Here's how each character is indexed: The last character's index is always the string length minus 1. For a 6-character string, the last index is 5. Negative Indexing Python has a powerful feature: n

  2. String Slicing (concepts: pySlicing)

    Slicing extracts a portion of a string using the syntax [start:end]. The slice includes the start index but excludes the end index. Slice Defaults Negative Slice Indices Negative indices work in slices too, making it easy to extract from the end: Slice Step A third parameter specifies the step (how many characters to skip):

  3. Searching in Strings (concepts: pyStringMethods)

    Python provides several ways to search for substrings within a string. Each method has specific use cases. The in Operator The first returns True (@ is in the email). The second returns False (gmail is not). find() and index() This finds @ at position 4, then searches for . starting from position 4, finding it at position 12. The count() Method This returns 3 (three a's), 2 (two "na" occurrences), and 0 (no x's). startswith() and endswith() These methods check if a string begins or ends with a s

  4. String Transformation

    Python provides methods to modify strings. Remember: strings are immutable, so these methods return new strings rather than modifying the original. strip(), lstrip(), rstrip() The stripped string is "hello world" with length 11. The original had length 17 including the spaces. You can specify which characters to strip: This removes dollar signs from both ends, giving "99.99". The replace() Method You can limit the number of replacements: This replaces only the first 2 occurrences, giving "1 two

  5. F-Strings (concepts: pyFStrings)

    Expressions in F-Strings You can put any expression inside the curly braces, not just variables: The expressions are evaluated, then the results are inserted into the string. Number Formatting F-strings support format specifications for controlling number display: F-strings have largely replaced concatenation as the preferred approach since Python 3.6. String Checking Methods Python provides methods to check string properties. These all return True or False. The username is alphanumeric (True),