Reverse String in Java DSA: Two Pointers That Swap in Place
-
Last Updated: July 27, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Reverse String in Java DSA made simple. Learn recursion, an extra-array version, and the optimal in-place two-pointer swap with dry runs and clean code.
Reverse String in Java is a classic warm-up for two-pointer thinking. You get an array of characters, and you flip it end to end. The catch is that you must do it in place, inside the same array.
The task looks tiny, yet it teaches a habit you will reuse everywhere. Instead of building a fresh array, you walk two pointers toward the middle and swap as you go.
We build the answer in three steps. Recursion swaps the outer pair, then calls itself on the inside. A helper-array version copies characters backward into a second array. The final two-pointer swap does the whole job in one flat loop with no extra array.
Every approach here comes with a full dry run. We trace the same word through each one, so you can watch the extra memory disappear step by step.
Let us fix the rules before we write any code. A clear problem statement now saves you from silent bugs later.
Notice one easy trap. The middle character of an odd-length word never moves. Only the pairs around it trade places.
In place means you edit the array the caller already holds. You do not build a copy and hand that back.
| 💡 Interview Insight Interviewers often ask what happens to the middle character in an odd-length array. The two-pointer loop simply stops before it, so the centre stays put. Saying this out loud shows you thought about odd and even lengths, not just one case. |
Two small ideas carry this whole problem. Both come back often in later array questions, so learn them well.
A swap trades two values without losing either one. You always need a temporary holder.
Here is the idea that makes this problem click. You keep two pointers walking toward each other through the same array.
The first idea many people reach for is recursion. Swap the two outer characters, then ask a smaller copy of the method to reverse the inside.
It reads elegantly, but it grows a call stack. Still, it is a good warm-up, and it proves you understand the goal.
reverse(s):
call helper(s, lower = 0, higher = last index)
helper(s, lower, higher):
if lower >= higher: // base case: nothing left to swap
return
swap s[lower] and s[higher] // swap this outer pair
helper(s, lower + 1, higher - 1) // recurse on the inner partpublic class ReverseStringRecursive {
public static void reverseString(char[] s) {
helper(s, 0, s.length - 1);
}
private static void helper(char[] s, int lower, int higher) {
if (lower >= higher) {
return;
}
char temp = s[lower];
s[lower] = s[higher];
s[higher] = temp;
helper(s, lower + 1, higher - 1);
}
public static void main(String[] args) {
char[] s = { 'h', 'e', 'l', 'l', 'o' };
reverseString(s);
System.out.println(new String(s)); // olleh
}
}Let us trace s = [h, e, l, l, o]. Watch the slice shrink on every call.
| Call | lower | higher | lower >= higher? | Swap done | array after |
|---|---|---|---|---|---|
| 1 | 0 | 4 | No | swap s[0] and s[4] | o e l l h |
| 2 | 1 | 3 | No | swap s[1] and s[3] | o l l e h |
| 3 | 2 | 2 | Yes | none, base case | o l l e h |
Legend: lower starts at the front, higher starts at the back, and each call moves both one step toward the centre.
Let us walk the whole trace, call by call, and see what each frame of the recursion does.
Call 1: lower = 0, higher = 4.
The first call gets the full array, with lower on the front slot and higher on the back slot. It checks the base case: is lower greater than or equal to higher? Here 0 is well below 4, so the answer is no, and the swap goes ahead.
So the two far ends trade places, and the array reads o e l l h. The call then recurses with lower moved in to 1 and higher moved in to 3.
Call 2: lower = 1, higher = 3.
This deeper call works on a smaller slice, the middle three characters. Again it asks whether lower has reached higher. Since 1 is still below 3, it swaps the next pair.
The word is already correct here, but the recursion does not know that yet. It recurses once more, with lower stepping to 2 and higher stepping to 2.
Call 3: lower = 2, higher = 2.
This is the base case. Both pointers have landed on the same middle index, so lower is now equal to higher. The check lower >= higher is true, and the call returns at once without swapping anything.
Now look at the hidden cost. Each call sits on the stack until the one below it returns.
Recursion touches each character once, but every call adds a stack frame. So the time is linear while the space is not constant.
The next idea drops recursion. You read the original from the back, and you write those characters, in order, into a fresh array.
It is flat and easy to follow. The weakness is that second array, which the problem told us to avoid.
n = length of s
copy = new array of size n
for i from 0 to n-1: // fill copy in reverse
copy[i] = s[n - 1 - i]
for i from 0 to n-1: // pour copy back into s
s[i] = copy[i]public class ReverseStringExtra {
public static void reverseString(char[] s) {
int n = s.length;
char[] copy = new char[n];
for (int i = 0; i < n; i++) {
copy[i] = s[n - 1 - i];
}
for (int i = 0; i < n; i++) {
s[i] = copy[i];
}
}
public static void main(String[] args) {
char[] s = { 'h', 'e', 'l', 'l', 'o' };
reverseString(s);
System.out.println(new String(s)); // olleh
}
}Same word. Watch copy fill from the back of s during the first loop.
| Step | i (write index) | reads s[n-1-i] | character | copy after this step |
|---|---|---|---|---|
| 1 | 0 | s[4] | o | o |
| 2 | 1 | s[3] | l | o l |
| 3 | 2 | s[2] | l | o l l |
| 4 | 3 | s[1] | e | o l l e |
| 5 | 4 | s[0] | h | o l l e h |
Let us go step by step through the first loop, then follow the second loop that copies the result home.
Loop 1, step 1: write index 0.
The write index i starts at 0, and the mirror read position is n minus 1 minus 0, which is 4. So we read s[4], the last character.
Loop 1, step 2: write index 1.
Now i is 1, so the mirror position is n minus 1 minus 1, which is 3. We read s[3].
Loop 1, steps 3 to 5: write indexes 2, 3, 4.
Each later step slides the read position one place further left, filling copy left to right.
Loop 2: copy back into s.
The helper now holds the answer, but the caller only ever reads s. So a second flat loop pours copy back into s, slot by slot, from index 0 to the end. After it finishes, s itself reads o l l e h.
Now look at the hidden cost. The helper array is a whole second array sitting in memory.
| 💡 Interview Insight A sharp follow-up asks whether you even need the second loop. If your language lets you return a new array, you could stop after building copy. But this problem demands in-place editing, so the copy-back is what makes the change visible to the caller. |
The two versions above both waste memory. Recursion grows the stack, and the helper builds a whole array. We can do better.
Keep one pointer at each end. Swap the pair they point at, then step both inward. When they meet, the array is reversed, and you never touched a second array.
lower = 0
higher = last index
while lower < higher:
swap s[lower] and s[higher] // swap this outer pair
lower = lower + 1 // step lower inward
higher = higher - 1 // step higher inwardpublic class ReverseStringTwoPointer {
public static void reverseString(char[] s) {
int lower = 0;
int higher = s.length - 1;
while (lower < higher) {
char temp = s[lower];
s[lower] = s[higher];
s[higher] = temp;
lower++;
higher--;
}
}
public static void main(String[] args) {
char[] s = { 'h', 'e', 'l', 'l', 'o' };
reverseString(s);
System.out.println(new String(s)); // olleh
}
}Now we trace the same word one final time. Watch the two pointers close in on the centre.
| Step | lower | higher | lower < higher? | Swap done | array after |
|---|---|---|---|---|---|
| 1 | 0 | 4 | Yes | swap s[0] and s[4] | o e l l h |
| 2 | 1 | 3 | Yes | swap s[1] and s[3] | o l l e h |
| 3 | 2 | 2 | No | loop stops | o l l e h |
Legend: lower is the front pointer, higher is the back pointer, and both step one place inward after each swap.
Let us go step by step and watch the two pointers close in on the centre.
Step 1: lower = 0, higher = 4.
The loop checks its condition first: is lower less than higher? Here 0 is below 4, so the loop body runs and a swap happens.
This one swap did two useful things at once. It moved o to the front, its final reversed position, and it carried h to the back, also its final spot. After the swap, lower steps up to 1 and higher steps down to 3.
Step 2: lower = 1, higher = 3.
The condition runs again: 1 is still below 3, so we swap the next pair inward.
After this swap, lower steps up to 2 and higher steps down to 2. Both pointers now sit on the same middle index.
Step 3: lower = 2, higher = 2.
The loop checks its condition one last time: is lower less than higher? Now 2 is not less than 2, so the condition is false and the loop stops before doing anything.
Same word, same answer, three very different amounts of work and memory.
| Approach | Passes over array | Extra memory used |
|---|---|---|
| Recursion | One, split across calls | A call stack up to n/2 deep |
| Extra array | Two flat loops | A second array of size n |
| Two-pointer swap | One flat loop | Two int pointers |
| 💡 Interview Insight Expect this question: why not use recursion, since it is elegant? Because deep recursion risks a stack overflow on very long arrays. The two-pointer loop uses constant extra space, so it stays safe no matter how long the input grows. |
Tables are precise, but a sketch often lands faster. Here is the same two-pointer trace drawn by hand.

The array sits along the top with its indices. Each step block on the left shows the swap and the pointer move. The panel below shows the array after that step.
Follow the colour cues as you read it.
All three give a correct answer. They just pay different prices for it.
| Approach | Time | Space | Note |
|---|---|---|---|
| Recursion | O(n) | O(n) | Clear, but grows the call stack |
| Extra array | O(n) | O(n) | Flat loops, but copies the array |
| Two-pointer swap | O(n) | O(1) | In place, single pass, expected answer |
Notice that all three run in linear time. The real story here is space, not speed. Both the recursion and the helper array cost O(n) extra memory, while the two-pointer swap carries just two variables.
In an interview, start by naming the simple options. Then explain the memory each one wastes. Finally tighten it into the two-pointer swap, which is the answer they expect.
| 💡 Interview Insight If asked to reverse only part of the array, mention that two pointers adapt easily. You just start lower and higher at the chosen bounds instead of the ends. The same swap loop then reverses only that stretch. |
A few small traps catch beginners here. Keep them in mind.
Run those last three cases through your code before you say you are finished. They catch more bugs than any normal input will.
A: Use two pointers. Put one at the start (lower) and one at the end (higher), swap the characters they point to, then move lower forward and higher backward. Stop when lower is no longer less than higher. This edits the same char array, so it uses O(1) extra space.
A: It stays put. When the string has an odd length, lower and higher meet on the middle index, so the loop stops before swapping it with itself. Only the pairs around the centre trade places.
A: Both run in O(n) time, but recursion adds a stack frame per call, so it costs O(n) extra space and can overflow on very long inputs. The two-pointer loop carries just two integer variables, giving true O(1) space.
A: The optimal two-pointer approach is O(n) time and O(1) space. The recursion and extra-array versions are also O(n) time but O(n) space, since they grow a call stack or allocate a second array.
Reverse String in Java looks like a warm-up, and in a way it is. But the two-pointer, in-place pattern inside it is a real skill.
Our five-character trace showed the payoff clearly. Recursion grew a stack, and the helper needed a second array of the same size. The two-pointer swap needed just two variables and a single sweep.
So take the pattern, not just the answer. Reach for a second array only when you truly must. The moment you can edit in place, a pair of pointers walking inward often does the whole job.