Reverse String: The In-Place Two-Pointer Swap
Reverse a character array in place with two pointers — the O(n) time, O(1) space swap pattern, a worked walkthrough, JavaScript code, and common mistakes.
This article has an interactive companion
Don't just read it — step through it interactively, one state change at a time.
Reverse String looks like a one-liner — and in most languages it is, if you're allowed to allocate a new array. The reason interviewers still ask it is the constraint tucked into the fine print: do it in place, with O(1) extra memory. That single rule turns a throwaway problem into the cleanest introduction to the opposing two-pointer pattern.
Get the swap-from-both-ends idea here and you've learned the mechanical core of palindrome checks, array rotations, and the partition step inside quicksort. It's a small problem that pays rent across your entire DSA toolkit.
The problem
Given an array of characters s, reverse it in place. You must modify the input array directly and may not allocate a second array to hold the result. Extra memory usage has to be O(1).
Input: s = ["h", "e", "l", "l", "o"]
Output: s = ["o", "l", "l", "e", "h"]Two constraints shape everything:
- In place — you mutate
sitself, not a copy. Nos = [...]reassignment to a fresh array. - O(1) space — a couple of index variables and one temp are fine; a second array of size n is not.
The brute force baseline
The instinct most people reach for is to build the answer in a new array by reading the input backwards:
function reverseString(s) {
const result = [];
for (let i = s.length - 1; i >= 0; i--) {
result.push(s[i]);
}
// copy back into s to satisfy "in place"
for (let i = 0; i < s.length; i++) {
s[i] = result[i];
}
}This produces the right characters, but it allocates a full result array of size n. That's O(n) extra space — exactly what the problem forbids. Popular shortcuts like s.reverse() or s.reduce() into a new string hide the same cost or dodge the in-place requirement entirely. In an interview, reaching for either signals you missed the actual point of the question.
The key insight
Reversing an array is really a set of mirror swaps. Position 0 has to end up holding whatever was at the last index. Position 1 swaps with the second-to-last. Every element trades places with its mirror image across the center — and the two middle-most elements are the last pair to swap.
That's the opposing two-pointer pattern: put one pointer at the far left, one at the far right, swap the characters they point at, then step both inward. When the pointers meet or cross, every mirror pair has been handled and the array is reversed. You never need scratch space because the swap happens directly inside s.
A single element sits alone in the middle of an odd-length array. It's already in its correct spot — its mirror position is itself — so the loop simply skips it once the pointers cross.
The optimal solution
This is exactly the algorithm the visualizer steps through:
var reverseString = function (s) {
let left = 0;
let right = s.length - 1;
while (left < right) {
let temp = s[left];
s[left] = s[right];
s[right] = temp;
left += 1;
right -= 1;
}
};The temp variable is load-bearing. Without it, s[left] = s[right] would overwrite the left character before you copy it into the right slot, and both positions would end up holding the same value. Saving the old left character first is what makes the three-line swap correct.
The loop condition is left < right, strictly less-than. When left === right (the lone middle element of an odd-length array) there's nothing to swap, so the loop stops one step early and does no wasted work.
Walkthrough
Trace s = ["h", "e", "l", "l", "o"]. Length 5, so right starts at 4 and exactly two swaps happen.
| Iteration | left | right | s[left] | s[right] | Array after swap |
|---|---|---|---|---|---|
| Start | 0 | 4 | "h" | "o" | ["h","e","l","l","o"] |
| 1 | 0 | 4 | "h" | "o" | ["o","e","l","l","h"] |
| 2 | 1 | 3 | "e" | "l" | ["o","l","l","e","h"] |
| Stop | 2 | 2 | — | — | left === right, exit |
After iteration 1, the outer pair h/o is locked into its final position. After iteration 2, e/l are placed. Now left is 2 and right is 2 — they're equal, so left < right is false and the loop exits. The l sitting at index 2 was already home. Final array: ["o","l","l","e","h"], reversed with zero extra allocation.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each of the ~n/2 iterations does constant work; n/2 swaps is still linear in n |
| Space | O(1) | Only left, right, and temp — three variables regardless of input size |
The brute-force copy is also O(n) time but pays O(n) space for the scratch array. The two-pointer version matches the time and drops the space to constant, which is the whole reason it's the expected answer.
Common mistakes
- Skipping the temp variable. Writing
s[left] = s[right]thens[right] = s[left]corrupts the data — after the first line,s[left]already equalss[right], so both slots get the same character. Always capture the old value first. - Using
left <= right. With<=, an odd-length array swaps the middle element with itself — harmless in result but a wasted operation, and it signals you didn't reason about the crossing condition. - Allocating a new array.
return s.reverse()or building a reversed copy violates the O(1)-space constraint. The problem specifically wants in-place mutation. - Off-by-one on
right. Initializeright = s.length - 1, nots.length. Starting ats.lengthreads past the end of the array on the very first swap.
Where this pattern shows up next
The opposing two-pointer idea — start at both ends, move inward — carries directly into other array problems:
- Single Number — another O(1)-space array trick where the clever move replaces the obvious extra allocation.
- Contains Duplicate — a linear array scan where recognizing the right data structure beats brute force.
- Longest Consecutive Sequence — turning an O(n log n) sort into an O(n) pass by reframing the problem.
- Product of Array Except Self — two directional passes over the array that echo the left-and-right sweep here.
You can also step through Reverse String interactively to watch the two pointers close in and each mirror pair snap into place.
FAQ
Why can't I just use the built-in reverse method?
You can produce the right output that way, but most built-ins either allocate a new array or hide the cost of one, which breaks the O(1)-space requirement the problem is testing. Interviewers ask Reverse String specifically to see whether you can implement the in-place swap yourself. Reaching for .reverse() skips the exact reasoning they want to observe. Write the two-pointer loop to show you understand the mechanics.
What is the time and space complexity of the two-pointer solution?
Time is O(n): the loop runs about n/2 times and each iteration does a constant-cost swap, which is still linear in the array length. Space is O(1): you only ever hold three variables — left, right, and temp — no matter how long the input is. That constant-space guarantee is what separates this solution from the naive copy-into-a-new-array approach, which costs O(n) memory.
Why does the loop use left < right instead of left <= right?
Because when left equals right, both pointers sit on the same element — the lone middle character of an odd-length array. That element is already in its correct reversed position, so swapping it with itself does nothing useful. Using strict less-than stops the loop exactly when every mirror pair has been swapped, avoiding a pointless self-swap. It's a small detail, but it shows you reasoned carefully about where the pointers meet.
Do I really need the temp variable to swap two elements?
In this language, yes — a straightforward swap needs somewhere to stash one value while you overwrite it. If you write s[left] = s[right] first, the original s[left] is gone before you can move it, so s[right] = s[left] just copies the value back onto itself and both slots end up identical. The temp variable holds the old left character across the two assignments. There are XOR or destructuring tricks that avoid a named temp, but they don't change the O(1) space cost and are harder to read.
Make it stick: run this one yourself
Don't just read it — step through it interactively, one state change at a time.