Free Study Material

Strings in Python

Introduction

You've used strings since the very first Python topic, always as a whole value. This chapter looks inside a string — at its individual characters — starting with how to reach any single one of them directly.

Concept

A string is an ordered, immutable sequence of characters (you already met immutability in the Data Types chapter). Each character has a fixed position, called an index. Python supports two ways of counting positions:

  • Positive indexing — starts at 0 for the first character, counting up (1, 2, 3, ...).
  • Negative indexing — starts at -1 for the last character, counting backwards (-2, -3, ...).

name[i] retrieves the single character at index i.

Worked Example

name = "PYTHON"

print(name[0])
print(name[3])
print(name[-1])
print(name[-6])

name has 6 characters, at positive indexes 0 through 5 and negative indexes -1 through -6. name[0] is 'P' (the first character). name[3] is 'H' (counting 0=P, 1=Y, 2=T, 3=H). name[-1] is 'N' (the last character). name[-6] is also 'P' — the same character as name[0], reached by counting backwards instead.

Warning: name[6] would raise IndexError: string index out of range — the last valid positive index of a 6-character string is 5, not 6, because indexing always starts at 0.

Explanation

Both indexing directions point at the exact same characters, just counted from opposite ends — name[0] and name[-6] are always the same character for a 6-character string. Negative indexing exists specifically so you can reach the end of a string (like the last character) without needing to know its exact length first.

Summary

A string is an immutable, ordered sequence of characters, each reachable by index — positive indexes count from 0 at the start, negative indexes count from -1 at the end — and indexing past the last valid position raises an IndexError.

What is a String?

A String in Python is a sequence of characters enclosed in quotes. It is an Immutable data type, meaning once you create a string, you cannot change individual characters inside it.

What is Indexing?

Indexing is the process of accessing a specific character from a string using its position number. Think of it like a house address: every character lives at a unique number.

Syntax

string_name[index_number]
  • string_name: The variable holding your text.
  • [ ]: Square brackets are used for indexing.
  • index_number: An integer representing the position (starting from 0).

Example:

word = "Python"
print(word[0]) # Prints 'P'

How Python "Sees" Text

Every character has a unique address called an Index.

P Y T H O N
0 1 2 3 4 5
-6 -5 -4 -3 -2 -1
# 1. Forward Indexing (Left -> Right)
text = "PYTHON"

print("First Letter [0]:", text[0]) 
print("Third Letter [2]:", text[2])
# 2. Backward Indexing (Right -> Left)
# Useful when you don't know the string length!

print("Last Letter [-1]:", text[-1])
print("Second Last [-2]:", text[-2])
# 3. Common Mistake: Out of Range
# Our string has length 6 (Indices 0 to 5)

# Uncommenting the next line will crash the program!
# print(text[10]) 

print("If you see this, the program is safe.")

Introduction

Indexing retrieves one character at a time. Slicing retrieves a whole piece of a string at once — a range of characters, in one step.

Concept

string[start:stop:step]
  • start — index to begin at (included). Defaults to 0 if left out.
  • stop — index to stop before (not included). Defaults to the end of the string if left out.
  • step — how many positions to move each time. Defaults to 1 if left out.

Worked Example

subject = "COMPUTER"

print(subject[0:4])
print(subject[3:])
print(subject[:3])
print(subject[::2])
print(subject[::-1])

subject[0:4] gives 'COMP' — characters at index 0, 1, 2, 3; index 4 is the stop, so it's excluded. subject[3:] gives 'PUTER' — from index 3 to the end, since stop was left out. subject[:3] gives 'COM' — from the start up to (not including) index 3. subject[::2] gives 'CMUE' — every second character, from the start. subject[::-1] gives 'RETUPMOC' — a step of -1 moves backwards through the whole string, which is the standard, idiomatic way to reverse a string in Python.

Note: The stop index in a slice is never included in the result — exactly like range() from the Iterative Statements chapter, which also stops one before its given stop value.

Explanation

Slicing never raises an IndexError, even for a range that runs past the end of the string — subject[3:100] simply returns everything from index 3 onward, with no error, unlike plain indexing (subject[100]), which does raise one. This makes slicing noticeably more forgiving to work with than indexing a single position.

Summary

string[start:stop:step] extracts a piece of a string — start is included, stop is excluded, and step controls the direction and gap between characters. Leaving any part out uses its default (start=0, stop=end, step=1), and step=-1 reverses the string.

Slicing is the process of extracting a part (substring) from a string. It allows you to "cut" a range of characters out of the main string.

Syntax

string_name[start : stop : step]
  • Start: The index where the slice begins (Included).
  • Stop: The index where the slice ends (Excluded).
  • Step: The jump value (Default is 1).
# The string we will slice
text = "COMPUTER"

# Indices:
# C  O  M  P  U  T  E  R
# 0  1  2  3  4  5  6  7

# 1. Standard Slice [start : stop]
# Take from index 0 up to (but not including) 3
part1 = text[0:3] 
print("First 3 letters:", part1)  # Output: COM

# Take from index 3 up to 6
part2 = text[3:6]
print("Middle letters:", part2)   # Output: PUT
text = "COMPUTER"

# 2. Leaving out Start (Defaults to 0)
# Same as text[0:4]
print("Prefix:", text[:4])  # Output: COMP

# 3. Leaving out Stop (Defaults to End)
# Same as text[4:8]
print("Suffix:", text[4:])  # Output: UTER

# 4. Leaving out Both (Copy Whole String)
print("Full Copy:", text[:])
text = "COMPUTER"

# 5. Using Step [start : stop : step]
# Take every 2nd character (0, 2, 4, 6)
print("Skip one:", text[0:8:2]) # Output: CMUE

# 6. Negative Step (REVERSING)
# Start at end, go to beginning, step -1
print("Reverse:", text[::-1])   # Output: RETUPMOC

Introduction

Beyond indexing and slicing, strings support the same kind of general operators you've already used elsewhere — plus a way to visit every character in order, one at a time.

Concept

  • Concatenation (+) — joins two strings into a new one.
  • Repetition (*) — repeats a string a given number of times.
  • Membership (in / not in) — checks whether one string occurs inside another.
  • len() — returns the number of characters in a string.
  • Traversal — visiting every character of a string in order, using a for loop.

Worked Example

first = "Good"
second = "Morning"

greeting = first + " " + second
print(greeting)
print(second * 2)
print(len(greeting))
print("Morn" in second)

for ch in first:
    print(ch)

first + " " + second concatenates three strings into "Good Morning". second * 2 repeats "Morning" twice, giving "MorningMorning". len(greeting) counts every character in "Good Morning", including the space, giving 12. "Morn" in second is True, since "Morn" genuinely occurs inside "Morning". The for ch in first: loop then traverses "Good" one character at a time, printing G, o, o, d on four separate lines — ch holds a fresh single character on each pass.

Note: + between two strings only ever concatenates them — it never adds their contents as numbers, even if both strings happen to contain digits. "12" + "3" gives "123", not 15.

Explanation

Traversal with for ch in first: works the same way as for num in range(...): from the Iterative Statements chapter — it's a loop that runs once per item, just with each "item" here being a single character of the string, visited automatically in order without needing indexes at all.

Summary

Strings support + (concatenation), * (repetition), in/not in (membership), and len() (character count). for character in string: traverses every character of a string in order, one pass per character.

1. Concatenation (+)

The + operator joins two or more strings together end-to-end.

"Hello" + "World"  →  "HelloWorld"

2. Repetition (*)

The * operator repeats a string a specific number of times.

"Ha" * 3  →  "HaHaHa"

3. Membership (in / not in)

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

"a" in "Apple"  →  False (Case Sensitive!)
"App" in "Apple"  →  True
text = "PYTHON"

# Method 1: Iterating by Element (Cleanest)
print("--- Method 1: Direct Access ---")
for char in text:
    print(char, end="-")
# Output: P-Y-T-H-O-N-

print("\n") # New line

# Method 2: Iterating by Index (More Control)
# Useful if you need to know the position of each letter
print("--- Method 2: Using Range & Index ---")
length = len(text) # Get total length (6)

for i in range(length):
    # i will be 0, 1, 2, 3, 4, 5
    print("Index:", i, "Value:", text[i])
# Program to count Vowels in a string
sentence = "Learning Python is Fun"
vowels = "aeiouAEIOU"
count = 0

print("Analyzing:", sentence)

for char in sentence:
    if char in vowels:
        print("Found vowel:", char)
        count = count + 1

print("Total Vowels found:", count)

Introduction

Beyond operators, strings come with many built-in methods — functions attached to a string itself, called using a dot. This topic covers the group that changes letter case.

Concept

  • upper() — returns a new string with every letter converted to uppercase.
  • lower() — returns a new string with every letter converted to lowercase.
  • title() — returns a new string with the first letter of every word capitalized.
  • capitalize() — returns a new string with only the very first letter of the whole string capitalized, and every other letter lowercased.
  • swapcase() — returns a new string with every uppercase letter made lowercase, and every lowercase letter made uppercase.

Worked Example

text = "hello WORLD"

print(text.upper())
print(text.lower())
print(text.title())
print(text.capitalize())
print(text.swapcase())
print(text)

text.upper() gives "HELLO WORLD". text.lower() gives "hello world". text.title() gives "Hello World" — the first letter of each word capitalized. text.capitalize() gives "Hello world" — only the very first letter of the entire string capitalized, and WORLD's letters are lowercased along with it. text.swapcase() gives "HELLO world". The final print(text) still shows the original, unchanged "hello WORLD".

Note: Every one of these methods returns a new string — none of them modify text itself. This is a direct consequence of strings being immutable, covered in the Introduction & Indexing topic. text.upper() on its own line, with no assignment, changes nothing at all.

Explanation

title() and capitalize() are easy to confuse because both involve capitalization, but they act at different scopes: title() treats every word independently, while capitalize() treats the whole string as a single unit and only ever touches its first character.

Summary

upper(), lower(), title(), capitalize(), and swapcase() each return a new, case-converted string without changing the original — because strings are immutable, the result must always be captured (printed or assigned) to be used.

.upper()

Converts all characters to UPPERCASE.

"Hello" → "HELLO"
.lower()

Converts all characters to lowercase.

"Hello" → "hello"
.capitalize()

Capitalizes only the first character of the string.

"hello world" → "Hello world"
.title()

Capitalizes the First Letter of Every Word.

"hello world" → "Hello World"
text = "pyThon PROgramming"

print("Original:", text)

# 1. Standard Conversions
print("Upper:", text.upper())
print("Lower:", text.lower())

# 2. Smart Conversions
print("Capitalize:", text.capitalize()) # Only 'P' becomes capital
print("Title Case:", text.title())      # 'Python' and 'Programming' become capital

Validation Cheat Sheet

Method Checks If String Contains... Example (Returns True)
isalpha() Alphabets ONLY (No numbers/spaces) "Apple"
isdigit() Digits ONLY "12345"
isalnum() Alpha-Numeric (Letters OR Numbers) "User123"
isspace() Whitespace ONLY (Space, Tab, Newline) " "
# Program to analyze a password string
password = "SecretCode123"

print("Analyzing:", password)

# 1. Check for digits
if password.isdigit():
    print("Weak Password: Contains only numbers!")
else:
    print("Good: Not just numbers.")

# 2. Check for alphanumeric (Letters + Numbers)
if password.isalnum():
    print("Format Valid: Contains letters and numbers.")
    
# 3. Check for pure alphabet
if password.isalpha():
    print("Warning: No numbers found!")
else:
    print("Good: Contains non-alphabet characters.")

Introduction

This topic continues the string methods you're building up — this time for cleaning up whitespace, searching for text inside a string, and replacing it.

Concept

  • strip() — returns a new string with leading and trailing whitespace removed. lstrip() removes it only from the left; rstrip() only from the right.
  • find(sub) — returns the index where sub first occurs, or -1 if it isn't found at all.
  • index(sub) — works like find(), but raises a ValueError instead of returning -1 if sub isn't found.
  • count(sub) — returns how many times sub occurs in the string.
  • replace(old, new) — returns a new string with every occurrence of old replaced by new.

Worked Example

raw = "   Computer Science   "

print(raw.strip())
print(raw.strip() + "|")

sentence = "the quick brown fox jumps over the lazy dog"
print(sentence.find("fox"))
print(sentence.find("cat"))
print(sentence.count("the"))
print(sentence.replace("lazy", "active"))

raw.strip() removes the leading and trailing spaces, leaving "Computer Science" — the | in the next line makes the missing trailing whitespace visible: Computer Science| with no gap before the |. sentence.find("fox") returns 16 (the index where "fox" begins). sentence.find("cat") returns -1, since "cat" never occurs. sentence.count("the") returns 2 — "the" appears twice (once at the very start, once later as part of "over the lazy dog"). sentence.replace("lazy", "active") returns the whole sentence with "lazy" swapped for "active", leaving every other word unchanged.

Warning: sentence.index("cat") would raise ValueError: substring not found instead of quietly returning -1 — use find() when a missing substring is a normal, expected possibility; use index() only when its absence should actually stop the program.

Explanation

find() and index() do the same search, but disagree on what "not found" means — one returns a sentinel value (-1) you're expected to check for, the other raises an error you're expected to handle or avoid. Picking the right one is really a decision about whether a missing substring is a normal outcome or a genuine problem in your specific program.

Summary

strip()/lstrip()/rstrip() remove whitespace; find() and index() both search for a substring's position but differ in how they report "not found" (-1 vs. a ValueError); count() counts occurrences; replace() returns a new string with every occurrence of one substring swapped for another.

.find(sub)

Returns the Index of the first occurrence.

Returns -1 if not found (Safe).
.index(sub)

Same as find(), but...

Crashes (Error) if not found.
.count(sub)

Returns the Number of times the substring appears.

text = "banana"

print("Original:", text)

# 1. Searching
print("Find 'a':", text.find('a'))    # Output: 1 (First occurrence)
print("Find 'z':", text.find('z'))    # Output: -1 (Not found)

# 2. Counting
print("Count 'a':", text.count('a'))  # Output: 3
print("Count 'na':", text.count('na')) # Output: 2

The "Strip" Family

  • .strip(): Removes whitespace from BOTH ends.
  • .lstrip(): Removes whitespace from the LEFT side only.
  • .rstrip(): Removes whitespace from the RIGHT side only.

The "Replace" Tool

text.replace(old, new)
# 1. SPLIT (Returns a List)
data = "Python-is-fun"
words = data.split("-")
print("Split Result:", words)
# Output: ['Python', 'is', 'fun']

# 2. JOIN (Returns a String)
# We use the connector string first!
new_sentence = " ".join(words)
print("Joined Result:", new_sentence)
# Output: "Python is fun"

# 3. PARTITION (Returns a Tuple of 3)
# Splits only at the FIRST occurrence
text = "email@example.com"
parts = text.partition("@")
print("Partition Result:", parts)
# Output: ('email', '@', 'example.com')

Introduction

This last group of string methods covers breaking a string apart into pieces, rebuilding one from pieces, checking how it begins or ends, and checking what kind of characters it contains.

Concept

  • split(separator) — breaks a string into a list of pieces, wherever separator occurs (a plain space, if left out).
  • join(list) — the reverse of split(): joins a list of strings into one string, placing the original string between each piece.
  • startswith(sub) / endswith(sub) — return True or False depending on whether the string starts or ends with sub.
  • isalpha() — True if every character is a letter, and there's at least one character.
  • isdigit() — True if every character is a digit.
  • isalnum() — True if every character is a letter or a digit.
  • isspace() — True if every character is whitespace.

Worked Example

line = "Rahul,85,Physics"

parts = line.split(",")
print(parts)

rebuilt = "-".join(parts)
print(rebuilt)

print(line.startswith("Rahul"))
print(line.endswith("Physics"))

print("Rahul".isalpha())
print("85".isdigit())
print("Rahul85".isalnum())

line.split(",") breaks "Rahul,85,Physics" wherever a comma occurs, producing the list ['Rahul', '85', 'Physics'] (lists are covered fully in the next chapter). "-".join(parts) reverses that, placing "-" between each piece to rebuild "Rahul-85-Physics". line.startswith("Rahul") and line.endswith("Physics") are both True. "Rahul".isalpha() is True (only letters). "85".isdigit() is True (only digits). "Rahul85".isalnum() is True (letters and digits are both allowed by isalnum(), but "Rahul85".isalpha() alone would have been False, since it contains digits too).

Note: split() and join() are opposites, and join() is called on the separator, not on the list — "-".join(parts), not parts.join("-"). This reads oddly at first, but it's exactly how Python defines it.

Explanation

split(",") is exactly how a single line of comma-separated data — like a student record read from a file — gets turned into individually usable pieces, which is precisely why this pattern reappears once file handling and CSV data are covered later in the course.

Summary

split(separator) breaks a string into a list of pieces; join(list), called on the separator string, rebuilds one. startswith()/endswith() check the ends of a string; isalpha()/isdigit()/isalnum()/isspace() check what kind of characters a string is made of, each returning True only if every character in the string qualifies.

1. len()

Returns the total number of characters in the string.

len("Hi There")  →  8 (Space is counted!)
.startswith(sub)

Returns True if the string begins with the specified value.

.endswith(sub)

Returns True if the string ends with the specified value.

Is it Upper or Lower?

  • .isupper(): Returns True if ALL characters are uppercase.
  • .islower(): Returns True if ALL characters are lowercase.

Note: Numbers and symbols are ignored by these checks.

# 1. Length Check
text = "Python 3.12"
print("Length:", len(text))  # Output: 11

# 2. Prefix/Suffix Check
filename = "homework.pdf"
if filename.endswith(".pdf"):
    print("This is a PDF file.")

url = "https://google.com"
if url.startswith("https"):
    print("This is a secure website.")

# 3. Case Status Check
password = "SECRET_CODE"
if password.isupper():
    print("Why are you shouting?")  # This will print

Want the full course?

Unlock all chapters, video lectures, and SQL practice exercises.

Get Full Access