Skip to content

How to Insert Substrings at Specific Indexes in Python

Learn Python string manipulation techniques to insert text at precise positions using slicing, f-strings, reusable functions, and efficient methods for multiple insertions.

String manipulation is a core skill in Python programming, and inserting text at specific positions is a common requirement for data formatting, template generation, and text processing. Since Python strings are immutable, meaning they cannot be modified after creation, inserting a substring requires constructing a new string from the original parts.

In this guide, you will learn how to insert text at any position within a string using slicing, f-strings, reusable functions, and techniques for handling multiple insertions efficiently.

String Slicing and Concatenation

The most straightforward approach uses slicing to split the original string at the target index, then concatenates the pieces with the new content in between:

original = "HelloWorld"
insert_text = " "
position = 5

result = original[:position] + insert_text + original[position:]

print(result)

Output:

Hello World

The pattern string[:index] + new_content + string[index:] works for any insertion point:

sentence = "Python is great"
word = "really "
index = 10

modified = sentence[:index] + word + sentence[index:]
print(modified)

Output:

Python is really great

Using F-Strings for Readability

F-strings provide a cleaner syntax for the same operation, especially when combining insertion with other formatting:

text = "ProgrammingFun"
insertion = " is "
pos = 11

result = f"{text[:pos]}{insertion}{text[pos:]}"

print(result)

Output:

Programming is Fun

This is particularly useful when the insertion is part of a larger formatted string:

filename = "reportpdf"
pos = 6

formatted = f"Document: {filename[:pos]}.{filename[pos:]}"
print(formatted)

Output:

Document: report.pdf

Reusable Insertion Function

Encapsulating the logic in a function makes it easy to reuse and handles edge cases cleanly:

def insert_at(original, substring, index):
    """
    Insert a substring at the specified index.

    Args:
        original: The source string.
        substring: Text to insert.
        index: Position for insertion (0-based).

    Returns:
        New string with the substring inserted.
    """
    if index < 0:
        index = max(0, len(original) + index + 1)

    return original[:index] + substring + original[index:]

print(insert_at("HelloWorld", " ", 5))
print(insert_at("Python", "3.", 0))
print(insert_at("Hello", "!", -1))
print(insert_at("Test", " Case", 100))

Output:

Hello World
3.Python
Hello!
Test Case

Multiple Insertions

When inserting at several positions in the same string, you must work from the highest index to the lowest. Otherwise, each insertion shifts the positions of all subsequent characters, causing later insertions to land in the wrong place.

A Common Mistake: Inserting in Forward Order

text = "ABCDEF"

result = text[:2] + "-" + text[2:]     # "AB-CDEF"
result = result[:4] + "-" + result[4:] # "AB-C-DEF" (wanted "AB-CD-EF")
print(result)

Output:

AB-C-DEF

The second dash ended up after C instead of after D because the first insertion shifted all positions by one.

The Correct Approach: Reverse Order

def insert_multiple(text, insertions):
    """
    Insert multiple substrings at specified positions.

    Args:
        text: Original string.
        insertions: List of (index, substring) tuples.

    Returns:
        Modified string with all insertions applied.
    """
    sorted_insertions = sorted(insertions, key=lambda x: x[0], reverse=True)

    for index, substring in sorted_insertions:
        text = text[:index] + substring + text[index:]

    return text

original = "ABCDEF"
changes = [(2, "-"), (4, "-")]

result = insert_multiple(original, changes)
print(result)

Output:

AB-CD-EF

By processing the highest index first, earlier positions remain accurate because the insertions do not affect characters before them.

List Conversion for Heavy Modifications

When performing many insertions, converting the string to a list of characters can be more efficient because list.insert() modifies the list in place rather than creating a new string each time:

def insert_many_via_list(text, insertions):
    """Efficient approach for numerous insertions."""
    chars = list(text)

    for index, substring in sorted(insertions, reverse=True):
        chars.insert(index, substring)

    return "".join(chars)

text = "ABCDEFGH"
insertions = [(2, "-"), (4, "-"), (6, "-")]

result = insert_many_via_list(text, insertions)
print(result)

Output:

AB-CD-EF-GH

Inserting at the Beginning and End

Two special cases deserve explicit mention since they are the most common insertion positions:

def insert_at(original, substring, index):
    if index < 0:
        index = max(0, len(original) + index + 1)

    return original[:index] + substring + original[index:]


text = "World"

result = "Hello " + text
print(result)

result = text + "!"
print(result)

print(insert_at(text, "Hello ", 0))
print(insert_at(text, "!", len(text)))

Output:

Hello World
World!
Hello World
World!

For these simple cases, direct concatenation is the clearest approach. The insert_at function is more valuable when the index is computed dynamically at runtime.

Method Comparison

Approach Best For Performance
Slicing + concatenation Single insertions O(n) per insertion
F-strings Readable single insertions with formatting O(n) per insertion
Reusable function Repeated use with edge case handling O(n) per insertion
Reverse-sorted multiple insertions Several insertions at known positions O(n) per insertion
List conversion + join() Many insertions in one string O(n) total

Conclusion

String insertion in Python is fundamentally an assembly operation: split the original string at the target index and join the pieces with the new content in between.

  • For single insertions, slicing with concatenation (original[:i] + new + original[i:]) is the simplest and most readable approach.
  • For multiple insertions, always process positions in descending order to prevent index shifting from corrupting later insertions.

When performing many modifications, convert to a list and use "".join() to avoid the quadratic cost of repeated string concatenation.

Navigation