Ping-pong Pair Programming - Learn one of the best pair programming techniques
Find out what pair programming is, and how to apply one of the best techniques to build higher-quality solutions more efficiently! As a bonus, you'll also learn how to use ping-pong pairing to practice and teach TDD.
Two heads are better than one, as the saying goes. And a lot of the time it really is true. That’s why pair programming, or pair programming, has been gaining so much traction around the world.
Pair programming
The basic idea is simple: just like in rally racing, where the driver and the navigator work together to win the race, in pair programming the pair works in a similar dynamic to reach a goal.

One programmer takes the driver role (and the keyboard), and the other takes the navigator role, responsible for following the work and anticipating the next steps. Every so often, the two switch roles.
Unfortunately, despite its huge potential for good results, it’s quite common to witness scenes like this one:

Or, after a few minutes of coding, you turn to ask your partner’s opinion only to find them distracted by their phone, with no idea what’s going on anymore. It’s a pretty unpleasant situation, and it can have several causes.
Maybe the two people don’t know how to work together, maybe one of them is having a bad day, but a much more frequent cause is a lack of discipline from at least one of them, or simply not knowing an effective way to work as a pair.
And that’s where the methodology known as Ping-pong Pair Programming comes in.
Ping-pong Pair Programming
Ping-pong Pair Programming is, in my humble opinion, an amazing methodology. Of all the pairing formats I’ve tried, it’s the only one that has proven foolproof at keeping the pair focused throughout the development process.

Instead of switching roles every so often, on a fixed interval, the ping-pong dynamic takes advantage of the TDD (Test Driven Development) workflow. The TDD lifecycle acts as a timer, setting the moment for each role switch.

Source: Agile in a Nutshell
When implementing a new feature with TDD, the pair will naturally go through the three phases of the cycle over and over.
For those who don’t know TDD, I’ll leave some reference material at the end of the article. But in short, the practice consists of letting the tests drive development: first we write the tests, and only then the production code, and only as much of it as it takes to make the test pass.
In the TDD lifecycle, we start by writing a test that naturally won’t pass (and, at first, won’t even compile). This test drives the implementation, and in it we express the result we expect from the code we’re about to write. If we stop and think about it for a moment, there’s no way to write the test first without understanding the business rule we need to implement. And the best part is that it kills that urge to start writing code without a clear plan.
Once we’ve written a test that fails because it’s waiting for the implementation, we’ve reached the end of the Red phase, and it’s time to switch roles. To take over as driver and implement the code needed to make the test pass, the other programmer has to be paying attention and actively participating, or they’ll be lost when their turn comes. Their job is to write the minimum code needed to make the test pass, reaching the end of the Green stage. Then it’s time to refactor whatever didn’t turn out so nice, both in the production code and in the tests.
And then it’s time to write a new test, leave it failing, pass the ball to your partner, and keep repeating the cycle.

A practical, step-by-step example
Nothing beats a bit of code to help visualize the development process with ping-pong pairing. Below we’ll see the very beginning of implementing a function that computes the Fibonacci sequence, written in Python.
The first thing the pair (Fulano and Ciclano, the Brazilian equivalent of John Doe and Joe Bloggs) should do is understand the business rule they intend to implement. Searching Wikipedia, they learn that the Fibonacci sequence is a sequence of integers that starts with 0 and 1, where each subsequent number is the sum of the two before it.
Fulano starts (PING)
The pair picks the driver, and he takes charge of starting the implementation. Following the good practice of baby steps, he writes the smallest test he can come up with:
import unittest
class FibonacciTests(unittest.TestCase):
def test_o_primeiro_valor_eh_zero(self):
valor = fibonacci(pos=0) # <-- this function doesn't even exist yet!
self.assertEqual(valor, 0)
if __name__ == '__main__':
unittest.main() Knowing the rule says the first value returned by the sequence is zero, the driver wrote a simple test that calls the future fibonacci function, passing pos 0, and expects the returned value to be 0. Having reached the end of the TDD cycle’s RED stage, he passes the ball.

Ciclano takes over (PONG)
Ciclano also knows about baby steps, and writes the smallest possible implementation to make the tests pass and reach the GREEN phase of the TDD cycle:
def fibonacci(pos):
return 0 He runs the tests, and they pass. Excellent.

Then it’s time to refactor (the REFACTOR stage). He looks at Fulano, and they conclude it’s still too early for that, so he moves on to writing the second test:
def test_o_segundo_valor_eh_um(self):
valor = fibonacci(pos=1)
self.assertEqual(valor, 1) 
He runs the test, and it fails (RED). Time to pass the ball. The code now looks like this:
import unittest
def fibonacci(pos):
return 0
class FibonacciTests(unittest.TestCase):
def test_o_primeiro_valor_eh_zero(self):
valor = fibonacci(0)
self.assertEqual(valor, 0)
def test_o_segundo_valor_eh_um(self):
valor = fibonacci(pos=1)
self.assertEqual(valor, 1)
if __name__ == '__main__':
unittest.main() Fulano takes over (PING)
First, he does the bare minimum to make the new test pass (GREEN):
def fibonacci(pos):
if pos == 1:
return 1
return 0 That was quick. Warm-up’s over. The two could stay in this loop of adding one value at a time for all eternity, but that wouldn’t be very productive. It’s time to make things interesting and turn up the heat! So Fulano writes the next test, forcing his partner to implement a version of the algorithm that handles the next values in the sequence (RED):
from unittest.mock import patch
# ...
@patch('builtins.input', return_value=[(2, 1), (3, 2), (4, 3), (5, 5), (6, 8), (7, 13)])
def test_o_proximo_valor_eh_a_soma_dos_dois_anteriores(self, retornos):
for par in retornos():
posicao, valor_esperado = par
valor = fibonacci(posicao)
self.assertEqual(valor, valor_esperado) 
Ciclano takes over (PONG)
Now things got more fun, and this round might take a bit longer. After thinking it over for a moment, he decides to implement the algorithm with a procedural approach, and arrives at this:
def fibonacci(pos):
anterior = 0
proximo = 0
for _ in range(0, pos):
proximo = proximo + anterior
anterior = proximo - anterior
if(proximo == 0):
proximo = proximo + 1
return proximo 
He runs the tests and they pass (GREEN). He decides to lightly refactor the code (REFACTOR), renaming the pos variable to posicao. The code now looks like this:
def fibonacci(posicao):
anterior = 0
proximo = 0
for _ in range(0, posicao):
proximo = proximo + anterior
anterior = proximo - anterior
if(proximo == 0):
proximo = proximo + 1
return proximo
# and the tests too: def test_o_primeiro_valor_eh_zero(self):
valor = fibonacci(posicao=0)
self.assertEqual(valor, 0)
def test_o_segundo_valor_eh_um(self):
valor = fibonacci(posicao=1)
self.assertEqual(valor, 1) The driver is a bit unsure about which test to write next. He asks his co-pilot Fulano if he has any ideas, and his friend reminds him that the Fibonacci sequence only covers positive numbers. They decide that in those cases, the function should return zero. So Ciclano writes this test:
def test_deve_retornar_zero_para_posicoes_negativas(self):
valor = fibonacci(-1)
self.assertEqual(valor, 0) When he runs this test, it passes, because coincidentally the for loop never runs. So it doesn’t count as RED, and he can’t pass the ball to his friend yet.

Looking at the Wikipedia article, he decides there may not be any more tests worth writing. He adds a few more cases to the test_o_proximo_valor_eh_a_soma_dos_dois_anteriores test and challenges his partner to refactor the function into the recursive version. His partner is in. They stay in the REFACTOR stage.
Fulano takes over (PING)
He starts refactoring the function, and arrives at this version, which satisfies every test written so far:
def fibonacci(posicao):
if posicao <= 0:
return 0
if posicao == 1:
return 1
else:
return fibonacci(posicao - 1) + fibonacci(posicao - 2) The two look at each other and nod: they did a good job. Fulano decides to move the function into a fib.py module, and they’re done. Time to go grab a coffee. :)
The fib.py file ended up like this:
def fibonacci(posicao):
if posicao <= 0:
return 0
if posicao == 1:
return 1
else:
return fibonacci(posicao - 1) + fibonacci(posicao - 2) And the test file ended up like this:
import unittest
from unittest.mock import patch
from fib import fibonacci
class FibonacciTests(unittest.TestCase):
def test_o_primeiro_valor_eh_zero(self):
valor = fibonacci(posicao=0)
self.assertEqual(valor, 0)
def test_o_segundo_valor_eh_um(self):
valor = fibonacci(posicao=1)
self.assertEqual(valor, 1)
@patch('builtins.input', return_value=[
(2, 1), (3, 2), (4, 3), (5, 5), (6, 8),
(7, 13), (8, 21), (9, 34), (10, 55)])
def test_o_proximo_valor_eh_a_soma_dos_dois_anteriores(self, retornos):
for par in retornos():
posicao, valor_esperado = par
valor = fibonacci(posicao)
self.assertEqual(valor, valor_esperado)
def test_deve_retornar_zero_para_posicoes_negativas(self):
valor = fibonacci(-1)
self.assertEqual(valor, 0)
if __name__ == '__main__':
unittest.main() All the code shown here, along with the sequence of commits made by our fictional pair, can be found in this repository.
Beware of overdoing it!
Pair programming is an intense activity, and it demands a lot from both people. It takes coordination and extra attention. If we overdo it, it can be exhausting. And the ping-pong dynamic is one of the most intense and most demanding on the programmer.

Make no mistake: the results can be amazing. In fact, the best pair programming session I’ve ever had was with this dynamic, alongside my friend Fernando Medeiros at Ambev Tech, in 2017. It was a fluid and productive experience at the same time. And also a rather tiring one. But we knew we had to keep a healthy balance in our routines to sustain that level of work.
It’s an illusion to think two people can pair all day long (I made that mistake myself a few years earlier), because a programmer has plenty of other things to do besides programming. You need to set aside time to read email, review your teammates’ code, attend meetings, and also for personal stuff.
So the tip is to avoid very long sessions. Two or three hours of ping-pong pair programming in a row are more than enough to bear good fruit.

Another tip is to plan breaks every so often to relieve the tension. If possible, take a short break every half hour. It’ll help keep your mind calm and focused.
Give your partner room to breathe
There are impatient people, and there are proactive people. Some programmers are both, and can’t watch their partner stop typing for a few seconds without jumping in with suggestions to “help them get unstuck”.
Helping matters, of course, and so does noticing when your pair gets stuck, but very often all your partner needs is about 10 seconds to put their thoughts together and plan the next step. Giving them that space is important for the experience to be pleasant for both of you.
How to use ping-pong pair programming to practice TDD?
Every TDD practitioner knows the beginning is hard, and for it to really click, you need a lot of practice. And it takes even longer for programming this way to become practically second nature.
For beginners, following the TDD cycle is counterintuitive. Our instinct says to just go and write that code right away. It says “we’ll write the tests later”.
Ping-pong pairing can be used to fight that urge, and to practice TDD until it becomes natural. There are two scenarios where this pairing strategy can help:
- Pairing an experienced TDD practitioner with a beginner
- Pairing two TDD beginners
In the first scenario, a programmer experienced in TDD can greatly speed up a beginner’s learning, with tips, good practices, and by discussing development strategies. It’ll be great for the beginner to watch the more experienced partner’s modus operandi.
If you’re an experienced practitioner, this is the ideal way to teach others and pass the practice on.
We don’t always have a TDD-experienced colleague available to pair with, but all an enthusiastic beginner needs is to find another one. It’ll be a bit harder and will take extra attention, but the two can “keep watch” over the process and reap great results. With some dedication, little by little, the TDD cycle will become more and more natural.
Wrapping up
I hope this short article inspires some people to invest in TDD, and others to spread the practice around, as I’ve been doing ever since I was introduced to it when I started working at Inventti in 2013. That’s when I became a TDD fan.