A pair programming platform to help you get better at technical interviews: building out the platform

Emmanuel Sibanda
CodingSquad
Published in
10 min readAug 29, 2022

Over the last few months I’ve engaged in a lot of pair programming, primarily in preparation of technical interviews. I found that getting on an audio call and pair programming with peers of different skill levels helped me better communicate my logic as I was problem solving, made the whole process of ‘grinding leetcode’ a lot more fun/tolerable and helped me get better at collaborating with others. I decided to build out a platform to facilitate this process for other people currently going through the mental and spiritual warfare of technical interviews. One could liken some technical interviews to walking into a battlefield with a plastic knife and getting one of your fingers cut off.

Tech interviews have you feeling like this?

How the platform works is relatively straightforward. People sign up, join a waiting room, get matched with another peer and start an hour-long pair programming session. This session would be carried out over a conference call between two parties with a shared code editor and access to hundreds of pair programming questions typically asked in interviews.

I am going to be writing a series of blog posts detailing features built into the MVP and beyond. In this blog post I will be detailing the process I followed to build out the code editor and hook it to Twilio to trigger an audio conference call when two parties join. For my tech stack I opted to use Django for the backend and React for the frontend.

High Level Design

To set the picture, imagine a page with a code editor, and two sidebars; one containing the question rendered and another rendering the output of the code written. The side bar on the left would have two tabs; one containing the actual question and giving users the option to skip/change the question shown and the other showing users what they should be doing. From having carried out multiple pair programming sessions, structure is very important to the overall experience. I boiled down the ideal structure of a pair programming session to involve;

i. an introduction : where users would introduce themselves. This is important in creating initial rapport.
ii.
pseudocode: a phase where users would read the question and pseudocode the potential logic required to solve said problem. If you can imagine a tech interview, this would be important in helping users practice laying out their logic before implementing a solution. It’s far easier solving a problem once you fully lay out the logic you would need to solve a problem, plus this presumably makes it easier for the interviewer to see that you understand what you are doing and give you more helpful hints along the way.
iii.
code: a phase to implement the logic laid out in the pseudocode phase
iv. review and rebuild: reviewing and optimizing the solution. Alternatively, if a solution cannot be found, finding pre-existing solutions, discussing them and trying to rebuild them. I found in my pair programming sessions that past a certain point, struggling to find a solution becomes counter-productive. You ideally try solving the problem for 20–30 minutes, if you cannot find a solution, search for a pre-existing solution and try to rebuild it. This helps avoid getting burnt out wasting time on one problem

The Build
There were a few components involved in this:

i. I needed to build the actual code editor.
ii. Find a way to get programming challenges either through pre-existing APIs or by enabling users to submit their own set of questions based on what they encountered in interviews. For the MVP, I opted to draw questions from the CodeWars API.
iii. Create a drop in audio conference call. Twilio offered very rich documentation and multiple tutorials. For this reason I opted to use Twilio’s API
iv. A timer to keep track of the amount of time that had passed. The length of time would be conditional based on the phase of the programming session
v. A code execution system that would be responsible for running the code and returning the correct output based on the code run and enabling users to run code for multiple languages
vi. A section in the sidebar to indicate to the user what session of the programming section they are currently in.

Building the code editor
To build out the code editor I opted to use react-ace as my package of choice. I chose this because there were over 340,000 weekly downloads, the package appeared to be relatively simple to use, with decent documentation to support building this out.

Timer and peer programming phases
Within the same file, I included a timer. I used React Countdown Circle Timer to set this up. Again my reasoning came primarily from the number of weekly downloads, ease of use and clearly laid out documentation. To factor in my customized use case (i.e. the countdown counts from a different a time for each phase of the programming session).

To set this up, I created a variable holding an array of values, representing the duration in which the clock would run for each phase. I did this in the parent component (the clock being the child of the CodeEditor) so I could use the timer to indicate the section of the programming session the users are currently in. For example if the clock runs through two elements in the timer array, the sidebar would indicate that the user has completed the introductory and pseudocode sections.

const array = [10,11,12,13];
const [key, setKey] = useState(1);
const [index, setIndex] = useState(1);
const [timer, setTimer] = useState(array[0]);

//event handler to change clock timer based on index
const handleComplete = () => {
setIndex(index + 1);
setTimer(array[index]);
setKey(key+1)
}
...
return (
<>
<div className="row">
<ClockCounter timer={timer} key={key} handleComplete={handleComplete} index={index} Result={Result} Button={Button} />
...

For the timer I defined a function that would increment the state of the index, key and timer to increment each time the clock ran to zero. Moving back to the clock, I added a ternary to conditionally run the clock with the duration set as a dynamic value, represented by the element in the array of values representing the duration of the clock. The ternary would run the clock while our index remained under 4 (because there are only 4 phases) alternatively returning a ‘session complete’ message if this condition is no longer applicable.

const ClockCounter = ({ key, index, timer, handleComplete, Result, Button }) => {
return (
<div className="App">
<div className="timer-wrapper">
{ index < 4 ?
<CountdownCircleTimer
...
key={index}
duration={ timer }
...
onComplete={handleComplete}
...
>
{renderTime}
</CountdownCircleTimer>
:
<Result
status="success"

I opted to pass down the index as a prop to the component containing the peer programming session phases. I opted to use the Steps from Ant Design to design the outline of look and feel of the programming phases on the sidebar.

Programming Challenges
To retrieve programming challenges, I opted to use the CodeWars API. Eventually, I would like users to be able to upload programming challenges based on interview questions they encountered. In the interim this would give the first few users an initial set of questions to work with. For this I used Context API to pass down the value retrieved from the axios request interacting with the CodeWars API down to the component containing the programming challenge. This was done with the assumption that the programming challenge may need to be passed down to multiple components. Since I was only looking to avoid drilling down props to multiple components, Context API seemed more useful and quicker to set up than Redux.

Linking the code editor to a code execution system
In order to render the code written out in my code editor, I would need to send the code written in my code editor to another platform that would execute the code and return the correct output. You can think of the the entire coding process as writing code, the code gets compiled and the file runs, the running of the file specifically relates to the execution of the code.

I opted to use Judge0 to execute my code. This was one of the only free, open source, well documented options I found, with a discord channel where the lead engineers were very responsive.

I started off by using the document interface method documet.getElementsByClassName to retrieve the code typed into the code editor, passing that, along with the language used, mapped from the number representing each programming language supported by Judge0, into a function that would post to the baseURL (you can get this from RapidAPI).

Once the code is posted to Judge0 a token is received. If the token represents a valid request, we do aGET request to the same url with the token appended proceeding a forward slash. Because there might be a lag between getting a response from with initial POST and doing the GET request, I used setTimeout to wait for a few seconds, wrapped in a promise. There are some APIs that expect success and/or failure callbacks to be passed, one such example being the setTimeout() function. I wrapped setTimeout in a promise to ensure that once the wait time is over, the resolved method passed down to the promise would be executed. I read that the best practice to deal with problematic functions like setTimeout. For example if it’s used in another function, in my case my GET request, if it fails or contains a programming error, nothing would catch it, is to wrap them at their lowest possible level and never call them directly again.

The GET request would return a JSON which would importantly contain stdout — “the standard output stream, which is a source of output from the program” and stderr — “standard error, the default file descriptor where a process can write error messages”. I would use the latter to show any errors rendered as a result of the code written and the former to show the user the output of the code rendered.

const threeSecondWait = () => {
return new Promise(resolve => setTimeout(() => resolve("result"),3000));
};
...
const sendCodeJudge0 = (requestBody) => {
axios.post(`${baseURL}`, requestBody, {
headers
})
.then((res)=> {
threeSecondWait().then(()=>{
axios.get(`${baseURL}/${res.data.token}`, {
headers
})
.then((res)=> {
setSpinnerOn(false)
!res.data.stdout ? setResp(res.data.stderr)
: setResp(res.data.stdout)
})
.catch((err)=> {
console.log('err',err)
})
})
})
}

Creating the conference call
To ensure a conference call is triggered when two users are redirected to the component containing the code editor, I set up a Twilio Conference call. To layout the process I am going to explain this in terms of the flow the user would follow and what happens at each step.

The user initially starts off in their profile dashboard. At this point they can then click a button ‘Join Waiting Room’ that would take them to a waiting room where they would remain until they are matched with a peer and redirected to their pair session. When the user clicks the Join Waiting Room button, a function runs that sends the user’s username to a RESTful API.

Within the RESTful API, I start off by instantiating a VoiceGrant, which is responsible for creating an access token for voice. For my use case, the VoiceGrant, takes a few parameters, an outgoing_application_sid taking in an environment variable containing the TWIML_APPLICATION_SID. This is SID that Twilio will look to when making outgoing calls and is required for using Twilio Voice. The access token is rendered as a JSON Web Token before it is returned to the client:

class TokenView(View):
def get(self, request, username, *args, **kwargs):
voice_grant = grants.VoiceGrant(
outgoing_application_sid=settings.TWIML_APPLICATION_SID,
incoming_allow=True,
)
access_token = AccessToken(
settings.TWILIO_ACCOUNT_SID,
settings.TWILIO_API_KEY,
settings.TWILIO_API_SECRET,
identity=username
)
access_token.add_grant(voice_grant)
jwt_token = access_token.to_jwt()
full_data = { 'token': jwt_token.decode()}
# print(type(jwt_token))
return JsonResponse(json.dumps(full_data), content_type="application/json", safe=False)

Moving back to what the user sees, once we have created a token for the specific user we initialize a device instance in the frontend using the token retrieved, updating it with a few attributes to help handle connectivity for lower bandwidth networks, control the bandwidth my VoIP application will use and the maximum duration of time the device will spend attempting to reconnect in the event of connectivity loss.

const setupTwilio = (nickname) => {
fetch(`http://127.0.0.1:8000/voice_chat/token/${nickname}`)
.then(response => response.json())
.then(data => {
// setup device
const twilioToken = JSON.parse(data).token;
const device = new Device(twilioToken);
device.updateOptions(twilioToken, {
codecPreferences: ['opus', 'pcmu'],
fakeLocalDTMF: true,
maxAverageBitrate: 16000,
maxCallSignalingTimeoutMs: 30000
});
device.on('error', (device) => {
console.log("error: ", device)
});
setState({... state, device, twilioToken, nickname})
})
.catch((error) => {
console.log(error)
})
};

The user is then redirected to the waiting room. In future blogs I will detail how the matching works within the waiting room and the rationale behind each step. However, in the interim, to summarize, a function wrapped inside a useEffect takes in the usernames of both matched user and sets a room name and participant Label. I opted to concatenate both usernames for both these values. We need both these attributes to create a Twilio conference, with the participantLabel

def post(self, request, *args, **kwargs):
request_body = json.loads(request.body)
room_name = request_body.get("roomName")
participant_label = request_body["participantLabel"]
curr_username = request_body.get('currUser')
matched_user = request_body.get('matchedUser')
print('username', curr_username)
current_user_id = User.objects.get(
username=curr_username).pk
matched_user_id = User.objects.get(
username=matched_user).pk
response = VoiceResponse()
dial = Dial()
dial.conference(
name=room_name,
participant_label=participant_label,
start_conference_on_enter=True,
)
print(dial)
response.append(dial)
return HttpResponse(response.to_xml(), content_type="text/xml")

This instructs Twilio to connect the caller to join a conference with the room name I created. The users are then redirected to their respective rooms (the component containing the code editor) where a new connection is attempted to the Twilio application associated with the access token used when instantiating the device instance”, taking in the room name and participant label passed down to this component from a global state.

In future blogs I will expand more on other steps followed in this process, specifically using websockets to redirect both users to their respective rooms, storing all ‘matchable users’ in a Redis cache and conditionally ending the conference call when the programming session ends.

Creating a global state
An integral part of passing down different states required in the creation of a Twilio Conference involved setting up a hook to update a global state. I created a context provider that I wrapped around the components that would require states dealing with the creation of a Twilio Conference call. I used createContext to create a context object, with the default value argument set as null and then used useContext to create the common states data that could be easily accessed throughout all the components wrapped under this context provider.

...
const RoomContext = createContext(null);

export const RoomContextProvider = ({ children }) => {
const[state, setState] = useState(initialState);
return (
<RoomContext.Provider value={[state, setState]}>{children}</RoomContext.Provider>
)
};

export const useGlobalState = () => {
const value = useContext(RoomContext)
if(value === undefined) throw new Error('Please add RoomContextProvider');
return value;
}

Here again, since I was primarily using this to pass down props I opted to use the useContext hook over Redux primarily because I wouldn’t need to do much state management here.

While I continue building this out, if you are keen to pair program to get a feel of how the platform will work, feel free to book a session through my Calendly. If you are keen to subscribe to my newsletter to stay up to date with this platform’s development and pre-sign up, follow this link. Or as the YouTubers would say; ‘smash that like and subscribe button’.

Until next time au revoir.

--

--