Perfect 💪 — Let’s begin Day 1 of your “Deep Python Strings in 3 Days” journey.

We’ll start with internals, memory behavior, string methods, and 8 fundamental exercises that build strong intuition.


🩵 DAY 1 – Python String Internals & Core Operations


🎯 Goal

Understand what actually happens inside Python when you work with strings — memory, immutability, slicing, interning — and then implement 8 foundation-level string exercises.


🧠 PART 1 – String Internals (Deep Dive)


🔹 1.1 What is a String in Python?

  • Strings in Python are immutable sequences of Unicode characters.
  • Each string is stored internally as a PyUnicodeObject (in CPython).
  • It holds:
    • The length
    • The hash value
    • A pointer to character data
    • Encoding details (1/2/4-byte per char depending on the widest codepoint)

Think of strings as read-only arrays of Unicode code points.


🔹 1.2 Immutability in Action

s = "hello"
print(id(s))
s += " world"
print(id(s))  # new object ID → new memory allocation

Explanation:

  • Each operation like concatenation or replace creates a new string object.
  • That’s why "".join(list_of_strings) is faster than repeated +=.

🔹 1.3 String Interning

Python interns some strings automatically for performance.

a = "python"
b = "python"
print(a is b)  # True → both point to same memory

x = "python_rocks!"
y = "python_rocks!"
print(x is y)  # False → not interned by default

import sys
y = sys.intern(y)
print(x is y)  # True after manual interning

Interning saves memory and speeds up equality checks, but never rely on it for logic (== is correct, not is).


🔹 1.4 String Slicing (copy behavior)

s = "abcdef"
slice1 = s[1:4]  # "bcd"
print(slice1)
print(id(s), id(slice1))  # different objects

✅ Slicing always creates a new string, but Python optimizes small slices (copy on write style).


🔹 1.5 Encoding and Decoding

s = "नमस्ते"
b = s.encode("utf-8")
print(b)  # bytes form
print(b.decode("utf-8"))  # back to str
  • Python’s str is Unicode, not bytes.
  • Always .encode() for file or network I/O.

🔹 1.6 Memory Efficiency Example

Bad ❌

result = ""
for i in range(10000):
    result += "a"

Good ✅

result = "".join(["a" for i in range(10000)])

🧰 PART 2 – Core String Methods

Below are groups of related methods and what happens behind the scenes:

CategoryExamplesWhat Happens Internally
Caseupper(), lower(), capitalize()Iterates all chars, creates a new copy
Whitespacestrip(), lstrip(), rstrip()Scans from ends to remove characters
Searchfind(), rfind(), startswith(), endswith()Efficient C substring search
Replacereplace(), split(), join()Single pass scanning; new string built
Validationisalpha(), isdigit(), isalnum(), isspace()Checks each character property
Formattingformat(), f"", %Creates new formatted string

💡 Tip: help(str) in REPL → see all available methods.


🧩 PART 3 – Hands-On Exercises

We’ll now implement 8 foundational problems.
Write and test these in day1_basics.py.


1️⃣ Is It a Vowel?

def is_vowel(ch: str) -> bool:
    return len(ch) == 1 and ch.lower() in "aeiou"

print(is_vowel("a"))
print(is_vowel("b"))

2️⃣ Reverse String

def reverse_string(s: str) -> str:
    return s[::-1]

print(reverse_string("hello"))

Behind the scenes:
s[::-1] creates a new copy by traversing from end to start.


3️⃣ Remove Vowels from a String

def remove_vowels(s: str) -> str:
    vowels = set("aeiouAEIOU")
    return "".join(ch for ch in s if ch not in vowels)

print(remove_vowels("Python Rocks"))

4️⃣ Replace Spaces with Hyphens

def replace_spaces(s: str) -> str:
    return s.replace(" ", "-")

print(replace_spaces("Deep Python Strings"))

5️⃣ Valid Anagram (LeetCode)

from collections import Counter

def is_anagram(s: str, t: str) -> bool:
    return Counter(s) == Counter(t)

print(is_anagram("listen", "silent"))  # ✅
print(is_anagram("rat", "car"))        # ❌

6️⃣ Remove Adjacent Duplicates

def remove_adjacent_duplicates(s: str) -> str:
    stack = []
    for ch in s:
        if stack and stack[-1] == ch:
            stack.pop()
        else:
            stack.append(ch)
    return "".join(stack)

print(remove_adjacent_duplicates("abbaca"))  # "ca"

7️⃣ Pangram Checker

def is_pangram(s: str) -> bool:
    letters = {ch.lower() for ch in s if ch.isalpha()}
    return len(letters) == 26

print(is_pangram("The quick brown fox jumps over a lazy dog"))

8️⃣ Longest Common Prefix

def longest_common_prefix(strs):
    if not strs:
        return ""
    prefix = strs[0]
    for s in strs[1:]:
        while not s.startswith(prefix):
            prefix = prefix[:-1]
            if not prefix:
                return ""
    return prefix

print(longest_common_prefix(["flower","flow","flight"]))  # "fl"

🧩 Bonus Mini Challenge

👉 normalize_string(s)

Task:
Implement a function that:

  1. Removes non-alphanumeric characters
  2. Converts to lowercase
  3. Replaces spaces with underscores
def normalize_string(s: str) -> str:
    return "".join(
        ch.lower() if ch.isalnum() else "_" if ch == " " else ""
        for ch in s
    )

print(normalize_string("Hello World! 2025"))
# "hello_world_2025"

📘 Day 1 Summary

ConceptKey Takeaways
ImmutabilityEvery modification → new object
InterningSame memory reused for small strings
join vs +=join is faster; precomputes size
EncodingUnicode under the hood; use .encode()
MethodsMost are O(n), return new strings
Patterns learnedStack-based cleanup, prefix comparison, Counter-based equality

🧠 End-of-Day Reflection Questions

  1. Why is Python string immutable?
  2. What is interning and why does it exist?
  3. Why is "".join(list) faster than concatenation in a loop?
  4. What’s the complexity of s.find(sub)?
  5. How would you manually reverse a string without slicing?

Would you like me to now generate the interactive Jupyter Notebook version of Day 1 (with markdown explanations + runnable code cells) so you can run and practice directly?