Docs · Practice

Your first practice run

So you want to practice — perfect, you’re on the right page. Below is a pretend interview: a task, a candidate writing code against it, and ten spare minutes of yours. Open Interview Cop next to this browser window and play along for real — the app can’t tell a practice screen from a live one.

Right now your app should look like this: only Start is lit up, and the hint square at the top already says what to aim at first.

Start by capturing the technical challenge — what the candidate is asked to solve.

Your app right now: Start is ready, the rest wakes up later.

1

Capture the technical challenge

Start takes a screenshot: press it, and your cursor turns into a crosshair for selecting any part of your screen.

Every session begins there — with the task itself, before a single line of code exists. The ticket, the doc, the message you sent: whatever the candidate is working from. Here’s the one our pretend candidate got:

challenge.mdthe task you sent the candidate
interview · 0 min in
select the challenge
1# The challenge — as sent to the candidate
2
3Build a rate limiter for our API.
4
5Rules:
6 - at most `limit` requests per user
7 - within a rolling window of `window_seconds`
8 - allow?(user_id) answers true or false
9
10Keep memory sane when users go quiet.

Try it now: in your app, press Start, then drag over the challenge above. One screenshot is enough here — if a real task were longer, you’d add the rest in a moment.

2

What just unlocked

The moment your first capture lands, the app itself tells you what to do — the hint square changes to:

Keep capturing — add more challenge details or candidate’s work as it unfolds. Use Next when ready

And two more buttons wake up: Next, and Run Cop wearing a small 1.

The 1 counts captures waiting to be analyzed — so far, just the challenge.

You could press Run Cop this second, and nothing bad would happen. But look at what it’s holding: one screenshot, the task, and nothing else. You’d get questions about the challenge — not about the person sitting in front of you. That’s the other half, and it’s the half you came for.

So keep capturing. Start belongs at the top of an interview, pressed exactly once. Next adds every screenshot after it to the same session, so the whole story stays in one place — what you asked for, and what the candidate has written so far.

3

Capture the candidate’s work

Let’s pretend the interview is 10 minutes in, and the candidate has written this much:

rate_limiter.rbcandidate's shared screen
interview · 10 min in
select about this much
1# Task: allow at most `limit` requests per user per window
2class RateLimiter
3 def initialize(limit, window_seconds)
4 @limit = limit
5 @window = window_seconds
6 @requests = Hash.new { |h, k| h[k] = [] } # user_id => [timestamps]
7 end
8
9 def allow?(user_id)
10 now = Time.now.to_f
11 recent = @requests[user_id].select { |t| now - t < @window }
12 return false if recent.size >= @limit
13
14 recent << now
15 @requests[user_id] = recent
16 true
17 end
18end

Try it now: press Next — not Start — and drag over the candidate’s code above. The whole window or just a slice: checking everything or one suspicious part is your call.

Run Cop now reads 2 — two captures waiting, the task and the code:

Two screenshots, one session. Now there's something to compare.

And the hint square moves on as well:

Use Run Cop to get AI questions based on current captures. You can keep adding more anytime.

4

Run Cop, then the arrow

Run Cop sends everything you’ve captured off for analysis. For a short while the button locks and fills with moving stripes:

Thinking. It usually takes a few seconds.

When the stripes are gone, the arrow button underneath starts glowing — and the hint says the same thing:

Your results are ready — click the arrow button below to open the panel.

The arrow opens the results panel.

More Transparent
More Opaque
Cop run #1a few seconds ago

Here are the questions you can ask the candidate:

  • 1

    The task asks you to keep memory sane when users go quiet. Where does that happen in your code?

    Listen for: nowhere yet — @requests keeps an entry for every user forever; a cleanup pass, or dropping empty lists on read, would do it

  • 2

    Why Hash.new with a block and not just {}? What does the block do?

    Listen for: the block inserts an empty array when you read a missing user — so even a plain read grows the hash

  • 3

    Two requests from the same user land at the same moment. Can both get through?

    Listen for: yes — both can read the same list before either writes back; the fix is a Mutex around check-then-add

Three button presses — Start, Next, Run Cop — and you’re already holding questions about decisions the author actually made, weighed against what you actually asked for. Isn’t that cool?

Noticed the window fading in and out? That’s the More Transparent / More Opaque header — slide your mouse along it and the whole results window changes opacity in real time. It’s deliberate, and it’s the feature you’ll love on a laptop.

Make the window translucent and you can read your questions and watch the candidate’s screen through them — at the same time, without ever closing or reopening anything. One mouse move to check the code, another to read the next question, while the candidate keeps talking.

5

The candidate kept typing

Back in your app, notice that Run Cop is locked again — grey, and with no number on it:

Counter back to zero: nothing new since the last run, nothing new to analyze.

The hint says it in the app’s own words:

Nice work — use Next to grab more screens, then hit Run Cop for updates

To generate new questions, it needs new context. Luckily, our pretend candidate didn’t sit still. Here’s their screen at the 20-minute mark:

rate_limiter.rbcandidate's shared screen
interview · 20 min in
select the current version
1# Task: allow at most `limit` requests per user per window
2class RateLimiter
3 def initialize(limit, window_seconds)
4 @limit = limit
5 @window = window_seconds
6 @requests = Hash.new { |h, k| h[k] = [] } # user_id => [timestamps]
7 Thread.new { loop { sleep(@window); cleanup! } }
8 end
9
10 def allow?(user_id)
11 now = Time.now.to_f
12 recent = @requests[user_id].select { |t| now - t < @window }
13 return false if recent.size >= @limit
14
15 recent << now
16 @requests[user_id] = recent
17 true
18 end
19
20 def remaining(user_id)
21 now = Time.now.to_f
22 recent = @requests[user_id].select { |t| now - t < @window }
23 [@limit - recent.size, 0].max
24 end
25
26 def cleanup!
27 now = Time.now.to_f
28 @requests.each_value { |ts| ts.reject! { |t| now - t >= @window } }
29 @requests.delete_if { |_, ts| ts.empty? }
30 end
31end

Try it now: press Next again and select the code above. Don’t hunt for just the new lines: simply select the current version of whatever you want to ask about.

The capture lands, and Run Cop wakes up with a 1 — one new screenshot since the last run:

The challenge and the first draft are still in the session — the 1 is just what's new.

Fresh context, ready to go. Press it. Same as before: stripes for a few seconds, then the arrow. This time the familiar window shows run #2 — follow-up questions that build on everything you’ve captured so far:

More Transparent
More Opaque
Cop run #2just now

New questions about the new code:

  • 1

    This thread you start in the constructor — when does it stop?

    Listen for: it never stops — every new RateLimiter leaks a thread; a stop method or a daemon thread would fix it

  • 2

    You clean old timestamps in two places now — allow? and cleanup!. Why two places?

    Listen for: allow? keeps the answers correct; cleanup! frees memory for users who went quiet — they do different jobs

  • 3

    allow? and remaining share the same three lines. Would you leave it like that?

    Listen for: pull those lines into one small helper method and call it from both places

6

That’s the whole loop

From here to the end of a real interview it’s the same three moves, over and over: NextRun Coparrow. Capture when the code changes, run when you want questions, read them straight off the glass.

StartonceNextRun Coprepeatuntil the interview ends

Hope that wasn’t too hard. It isn’t — that’s the point.