Skip to content
Hello Python
Data Structure3 Practice12 Interview

String

Immutable character sequence used in parsing, matching, sliding-window, and dynamic-programming tasks. This guide turns that contract into deliberate Python operations, interview invariants, and a runnable practice loop.

Recognize it when

Consider String when the prompt's constraints and required operations match this shape: Immutable character sequence used in parsing, matching, sliding-window, and dynamic-programming tasks.

Pybit studies a professional String interview workspace with precise technical objects.
On this page · Mental Model

Checking your account…

Sign in to save your code and progress across devices. The lesson and problem statement remain public.

String Code Labs

Mental Model

A Python string is an immutable sequence of Unicode code points. An index identifies a position in that sequence, but no operation can replace a character in place. Every transformation must either reuse the original object or construct a new string. This ownership fact explains both the clean API and the hidden cost of careless concatenation.

For a scan, define what the processed prefix means. If count is the number of normalized vowels in text[:index], reading text[index] extends that proof by exactly one character.

Immutability and Python String Costs

Indexing reads one character. A slice copies the selected range, so slicing k characters costs O(k) time and space. Membership in a short fixed literal such as "aeiou" is bounded by that literal’s size, while membership in a long string is linear in the searched text.

Normalization is part of the contract. lower() is often enough for an ASCII interview prompt; casefold() is the stronger Unicode-oriented normalization. Neither operation mutates the source, and both may allocate a new string.

Scan Normalized Characters

Reference
def count_vowels(text):
    vowels = set('aeiou')
    return sum(character.casefold() in vowels for character in text)
Practice

Implement count_vowels(text). Count ASCII vowels without changing the input, treating uppercase and lowercase letters equally.

Public tests

  • Verify mixed-case normalization
  • Verify immutable source semantics

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Build Output Without Recopying Prefixes

Repeated result += piece can copy the growing prefix on each iteration. Accumulate pieces in a list, then call "".join(parts) once. This makes the construction cost proportional to the total output size rather than the sum of every intermediate prefix.

Use a list comprehension when each input produces one independent piece. Use an explicit loop when the next piece depends on parsing state, escaping, or a previous character.

Build Normalized Words Once

Reference
def normalize_words(words):
    parts = []
    for word in words:
        normalized = word.strip().lower()
        if normalized:
            parts.append(normalized)
    return ' '.join(parts)
Practice

Implement normalize_words(words). Strip and lowercase each non-empty word, then join the results with one space.

Public tests

  • Verify one final joined output

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Parse with an Explicit Boundary Contract

A delimiter alone is ambiguous when the original strings may contain that delimiter. A length prefix makes the boundary self-describing: write the decimal length, a separator, then exactly that many characters. During decoding, find the separator, parse the length, and advance by the stated payload size. The invariant is that index always points at the beginning of the next length field.

Encode Explicit String Boundaries

Reference
def encode_strings(values):
    return ''.join(f'{len(value)}#{value}' for value in values)

def decode_strings(payload):
    values = []
    index = 0
    while index < len(payload):
        separator = payload.index('#', index)
        length = int(payload[index:separator])
        start = separator + 1
        end = start + length
        values.append(payload[start:end])
        index = end
    return values
Practice

Implement encode_strings(values) and decode_strings(payload) with decimal length prefixes followed by # and the exact string payload.

Public tests

  • Verify ambiguous payload boundaries
  • Verify empty collection round trip

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

This approach handles empty strings and delimiter characters without escaping. Its work is linear in the encoded output size. Python string lengths count Unicode code points, which is consistent because the encoder and decoder use the same representation in the same runtime.

Choose a String Technique

Use indices when you only need boundaries; taking a slice on every iteration adds copying. Use a list of characters when positions must be edited before producing the final string. Use a set or dictionary for repeated membership/count queries, and a sliding window when the answer concerns a contiguous substring whose state can be updated incrementally.

Do not sort merely to compare strings when a frequency table preserves all required information in linear time. Conversely, sorting can be the clearest canonical signature when the alphabet is not small and the additional O(k log k) work fits the constraints.

Common Pitfalls

  • Calling strip() without a specification can remove meaningful leading or trailing characters.
  • Splitting on a delimiter loses information when fields may contain that delimiter or be empty.
  • Building every candidate substring with text[left:right] can add quadratic copying.
  • Treating user-visible grapheme clusters as single Python characters is unsafe for general Unicode text; interview prompts normally define a simpler character model.

Explain It in an Interview

Say which unit the prompt calls a character, whether normalization is required, and whether indices or copied substrings are stored. While coding, state the prefix or parser-boundary invariant. When giving complexity, include every created slice and the size of the final output, not just the number of loop iterations.

Count Vowels(opens in a new tab) practices a normalized scan. Encode and Decode Strings(opens in a new tab) then tests whether the boundary contract survives empty values and payloads containing punctuation.

Python Version Note

Complexity & Invariants

Interview bounds depend on the stated representation, input model, and real Python operations.

OperationAverageWorstInterview note
Core String workflowO(n)O(n)Lowercase each character and count membership in the five-vowel set.

Space

O(1) for the demonstrated String workflow.

Assumptions

  • Exact bounds depend on copying, slicing, insertion position, and whether a new result is materialized.
  • The bound counts the operations in Count Vowels and does not hide Python slicing, sorting, or copying.

Invariants Worth Saying Aloud

  1. State the precise String invariant before coding and preserve it after every update, traversal step, or recursive return.
  2. Scan characters and test normalized membership. This remains true after every accepted operation.

When To Use Or Avoid String

Use It When

  • Use String when its representation directly supports the prompt's repeated query or update.
  • Use it when the invariant can be stated before coding and preserved after every operation.

Choose Another Tool When

  • Avoid it when a simpler Python sequence or mapping communicates the same contract with less state.
  • Avoid it when building the structure costs more time or space than the number of required queries can justify.

Common Pitfalls

These are the mistakes most likely to survive a happy-path example and fail a boundary case.

Leaving the core transition unfinished

Placeholder code cannot preserve the String invariant or satisfy the public contract.

Prevent it: Implement the smallest complete transition and run the public tests before adding optimizations.

Avoid

def count_vowels(text):
    pass

Use instead

def count_vowels(text):
    return sum(character.lower() in 'aeiou' for character in text)

Breaking the central invariant

This implementation ignores uppercase vowels and undercounts mixed-case input.

Prevent it: Keep this invariant visible while editing: State the precise String invariant before coding and preserve it after every update, traversal step, or recursive return.

Avoid

def count_vowels(text):
    return sum(character in 'aeiou' for character in text)

Use instead

def count_vowels(text):
    return sum(character.lower() in 'aeiou' for character in text)

Hiding Python work inside the loop

Handle empty state, duplicate values, aliasing, and serialization boundaries explicitly in tests.

Prevent it: Account for every slice, copy, sort, membership test, and container mutation before claiming O(n).

Avoid

def count_vowels(text):
    return sum(character in 'aeiou' for character in text)

Use instead

def count_vowels(text):
    return sum(character.lower() in 'aeiou' for character in text)

Reviewed References

Official Python documentation supports language behavior. Canonical problem pages provide additional practice context.