# Remove a Character from a String: In-Place, Two-Pointer, Stream

Source: https://www.techinterview.org/post/3233459622/removing-a-character-from-a-string/
Updated: 2026-04-27 · techinterview.org

## Remove a Character from a String: In-Place, Two-Pointer, and Stream Variants

Removing characters from a string is a foundational interview problem that tests in-place manipulation, two-pointer technique, and the realization that strings are immutable in many languages. The base problem — remove all occurrences of a target character — is simple enough to solve in 30 seconds. Interviewers use it as a warm-up, then push into harder variants: remove duplicates, remove characters matching a set, remove the kth occurrence, or stream-based filtering. This guide covers all the standard variants with working code.

## Problem Statement

Given a string `s` and a character `c`, return a new string with all occurrences of `c` removed.

Examples:

- `s = "hello world"`, `c = 'l'` → `"heo word"`

- `s = "abc"`, `c = 'd'` → `"abc"`

- `s = ""`, any c → `""`

## Approach 1: List Comprehension / String Builder

Idiomatic in Python: filter and join.


```
def remove_char(s: str, c: str) -> str:
    """O(n) time, O(n) space — idiomatic Python."""
    return "".join(ch for ch in s if ch != c)


# Equivalent
def remove_char_alt(s: str, c: str) -> str:
    return s.replace(c, "")
```


`str.replace` is the practical answer in Python. **Complexity:** O(n) time, O(n) space (new string).

For interview purposes, write the comprehension version to demonstrate you can solve it manually, then mention `replace`.

## Approach 2: In-Place with Two Pointers (For Mutable Arrays)

If the string is given as a mutable character array (Java, C++, or a list-of-characters in Python), use two pointers to remove in place. Read pointer scans the array; write pointer fills positions to keep.


```
def remove_char_inplace(chars: list[str], c: str) -> int:
    """Modify chars in place; return new length.
    O(n) time, O(1) extra space."""
    write = 0
    for read in range(len(chars)):
        if chars[read] != c:
            chars[write] = chars[read]
            write += 1
    return write


# Test
chars = list("hello world")
new_len = remove_char_inplace(chars, "l")
print("".join(chars[:new_len]))  # "heo word"
```


**Complexity:** O(n) time, O(1) space. The standard answer when interviewers specify "in place" or "no extra string allocation."

## Approach 3: Filter Multiple Characters

Generalize: remove all characters matching a set. Useful follow-up.


```
def remove_chars(s: str, to_remove: set) -> str:
    """O(n) time, O(n) space."""
    return "".join(ch for ch in s if ch not in to_remove)


# Test
print(remove_chars("hello world", {"l", "o"}))  # "he wrd"
```


Use `set` for O(1) lookups. `str.translate` in Python is the optimized stdlib alternative:


```
def remove_chars_translate(s: str, to_remove: str) -> str:
    """O(n) time using str.translate."""
    return s.translate(str.maketrans("", "", to_remove))
```


## Approach 4: Remove Duplicates

(LeetCode #316, #1047 — Remove Adjacent Duplicates) Remove consecutive duplicate characters using a stack.


```
def remove_adjacent_duplicates(s: str) -> str:
    """O(n) time, O(n) space — stack-based."""
    stack = []
    for ch in s:
        if stack and stack[-1] == ch:
            stack.pop()
        else:
            stack.append(ch)
    return "".join(stack)


# Tests
print(remove_adjacent_duplicates("abbaca"))  # "ca"
print(remove_adjacent_duplicates("aaaa"))    # ""
```


The stack pattern repeatedly cancels adjacent equal characters, naturally handling the "after removal, new adjacencies appear" case.

## Approach 5: Stream Filtering

For very large strings or true streaming use cases, generators avoid loading the full string in memory.


```
def filter_stream(stream, c):
    """Generator that yields characters from stream, skipping c."""
    for ch in stream:
        if ch != c:
            yield ch


# Usage with file streaming
# with open("large.txt") as f:
#     filtered = "".join(filter_stream(f.read(), "l"))
```


The interview rarely demands streaming, but mentioning the generator approach signals memory awareness.

## Common Variations

### Remove all whitespace

`s.replace(" ", "")` for spaces, or `"".join(s.split())` to also collapse tabs/newlines. Or use `str.translate` with a whitespace set.

### Remove kth occurrence

Track a counter; remove only when counter matches k. O(n) time.


```
def remove_kth_occurrence(s: str, c: str, k: int) -> str:
    result = []
    count = 0
    for ch in s:
        if ch == c:
            count += 1
            if count == k:
                continue
        result.append(ch)
    return "".join(result)
```


### Remove all but k occurrences

Variant: keep the first k occurrences, remove the rest. Useful for deduplication-style problems.

### Remove according to a predicate

Generalize to any predicate function. Most languages have stdlib filter equivalents (Python's `filter()`, JavaScript's `Array.filter()`, C++'s `std::remove_if`).

### Sliding window with character removal

(LeetCode #3 — Longest Substring Without Repeating Characters) Different problem family. Sliding window pattern; remove characters from a window, not the string itself.

## Common Mistakes

- **Treating Python strings as mutable.** Strings are immutable; `s[i] = "x"` raises TypeError. Convert to list, modify, join. Or use the comprehension approach to build a new string.

- **Using `+= ` in a loop to build the result.** In Python, `result += ch` in a loop is technically O(n²) due to string immutability and reallocation. Use a list with append and join at the end. CPython has internal optimizations that sometimes hide the cost, but the pattern is fragile.

- **Off-by-one in the two-pointer in-place version.** The write pointer must advance only when the current character is kept; the read pointer always advances. Mixing up these conditions corrupts the output.

- **Returning new length but leaving stale data.** If the caller uses the array after in-place removal, they should read only up to the returned length. Some implementations zero out the trailing positions; usually unnecessary.

- **Confusing "remove all occurrences" with "remove first occurrence."** Read the prompt carefully. `str.replace` removes all by default; `s.replace(c, "", 1)` removes only the first.

## Frequently Asked Questions

### What's the expected interview answer?

  List comprehension or two-pointer in-place, depending on whether the string is mutable in the interview's language. Walk through both. Mention `str.replace` as the production-equivalent. The interviewer is checking whether you understand string immutability and two-pointer mechanics; clean code in either approach is sufficient.

### Why is in-place removal preferred when applicable?

  O(1) extra space versus O(n) for the new-string approach. For huge inputs (gigabyte-scale buffers, embedded systems with tight memory), in-place matters. For interview-sized inputs, the difference is negligible; the question is about technique, not performance.

### How do I handle Unicode correctly?

  Python strings are sequences of Unicode code points by default; iteration yields code points, not bytes. `str.replace` handles Unicode correctly. The two-pointer approach also works on lists of code points. Issues arise only at the byte level (e.g., when the string is encoded as UTF-8 bytes); avoid byte-level operations on Unicode strings.

### What's the most common follow-up?

  Remove adjacent duplicates (LeetCode #1047) using a stack. The pattern teaches a different technique while sharing the "manipulate a string" theme. Other follow-ups: remove characters from a set; remove kth occurrence; sliding window for longest substring without repeats.

### How does this generalize to other languages?

  Java: convert to `char[]` for in-place; use `StringBuilder` for new-string approach; or `String.replace`. C++: use `std::remove` + `erase` idiom (the "remove-erase" pattern). JavaScript: `String.replace` with global flag, or `Array.filter` + `Array.join`. Go: `strings.ReplaceAll`. The two-pointer pattern is universal; the language-idiomatic stdlib varies.

See also: [Longest Palindromic Substring](/post/3233459618/find-longest-palindrome-in-a-string/) • [Print String Permutations](/post/3233459702/longest-palindrome-in-a-string/) • [Two Sum: Find a Pair in an Array](/post/3233459633/sum-up-a-pair-in-array/)
