Skip to main content
Practice questions · Checked November 3, 2026

Karat Interview Questions: 6 Practice Problems in Parts

These are 6 original Karat-style coding problems, each in parts 1, 2 and 3, plus 6 short discussion questions with model answers. A Karat interview is 60 minutes with a Karat Interview Engineer: a short intro, about 10 minutes of discussion questions, then about 40 minutes of coding. Each problem here has examples, talking points, a tested Python 3 solution and what the interviewer will look for. None of them are real Karat questions, and a Karat redo comes with new questions anyway, so practice the patterns.

The Karat interview in 30 seconds

  • A Karat interview is 60 minutes with a Karat Interview Engineer: a short intro, about 10 minutes of discussion questions, then about 40 minutes of coding.
  • Karat says the most important thing it evaluates is how successfully your code solves the problem, and that it is not a race: work steadily and do not get stuck on one approach.
  • Every interview is recorded with audio and a code replay, and the company gets a report with a recommendation, a score and notes on each question.
  • Some interview types include Karat's built-in AI assistant, and you can ask for a redo within 24 hours, with new questions.

Scoring, proctoring and retakes are in our Karat guide.

A 60-minute practice session

  1. 1Minutes 0-5: introduce yourself in two or three sentences.
  2. 2Minutes 5-15: answer 3 or 4 discussion questions out loud, about 2 minutes each.
  3. 3Minutes 15-55: one problem below. Aim to finish Part 1 by minute 25 and Part 2 by minute 40, then take Part 3.
  4. 4Minutes 55-60: say what you would test or improve next, and ask a question of your own.

The 6 Karat practice problems

Each problem has three parts that build on each other, which suits a 40-minute coding block. Finish each part cleanly before you read the next one.

#ProblemDifficultyPattern
1Theater seat finderEasyString scanning
2Transit card faresMediumStateful simulation
3Playlist repeat ruleMediumHash map of last positions
4Warehouse robot commandsMediumGrid simulation
5Sensor stability windowsHardMonotonic deques
6Courier mapHardBFS, then Dijkstra
Problem 1EasyString scanning

Theater seat finder

def free_per_row(rows), def find_block(rows, k, spaced=False)

A theater seat map is a list of strings, front row first. 'X' is a taken seat and '.' is a free seat.

  1. Part 1. Write free_per_row(rows), which returns the number of free seats in each row.
  2. Part 2. Write find_block(rows, k), which returns [row, col] for the first block of k free seats side by side, searching front to back and left to right, or None if there is none.
  3. Part 3. Now the group wants space. With spaced=True, find the first block of k free seats where the seat just left of the block and the seat just right of it are also free, or are the edge of the row.

Example

Input
rows = ["X..X...", "....XX.", "X......"]
free_per_row(rows)
find_block(rows, 3)
find_block(rows, 3, spaced=True)
Output
[5, 5, 6]
[0, 4]
[1, 0]
Why
The block at row 0, col 4 has a taken seat on its left, so the spaced search moves on to row 1, where the block touches the row's left edge.
Show approach and solution

Say this out loud

  • Say out loud that "first" means front to back, then left to right.
  • Ask whether an aisle splits a row. Here each row is one straight line.
  • The slice check is O(k) per position; mention that a running count of free seats would make it O(rows x cols).

Approach

Part 1 counts dots per row. Parts 2 and 3 try every starting column in every row, check that the next k seats are free, and for the spaced version also check the seat on each side (or the edge). The first match is returned.

Python 3 solution
def free_per_row(rows):
    return [row.count('.') for row in rows]


def find_block(rows, k, spaced=False):
    for r, row in enumerate(rows):
        for c in range(len(row) - k + 1):
            if row[c:c + k] != '.' * k:
                continue
            if spaced:
                left_ok = c == 0 or row[c - 1] == '.'
                right_ok = c + k == len(row) or row[c + k] == '.'
                if not (left_ok and right_ok):
                    continue
            return [r, c]
    return None

Complexity: O(rows x cols x k) time as written, O(1) extra space.

What the interviewer will look for: k larger than the row, k = 1, a block touching the row's edge, rows that are all taken, and returning None when nothing fits.

Problem 2MediumStateful simulation

Transit card fares

def total_fare(taps, transfers=True, cap=True)

A transit card logs taps as [minute, station] pairs in time order, where minute counts from the start of the month. Every tap starts a ride. Money is in cents.

  1. Part 1. Each ride costs 275 cents. Return the total for the month.
  2. Part 2. Now add transfers: a ride that starts at most 120 minutes after the most recent paid ride is free. Each paid ride allows only one free transfer; the ride after that is paid again.
  3. Part 3. Now add a daily cap: once the rider has paid 1,200 cents in one calendar day (minute // 1440), the rest of that day's rides are free. The ride that crosses the cap costs only what is left up to the cap, and still counts as paid.

Examples

Input
taps = [[0, "A"], [60, "B"], [100, "C"], [200, "A"]]
total_fare(taps, transfers=False, cap=False)
total_fare(taps, cap=False)
Output
1100
550
Why
With transfers, the rides at 60 and 200 are free: each comes within 120 minutes of a paid ride that has not used its transfer.
Input
taps = [[0, "A"], [300, "B"], [600, "C"], [900, "D"], [1200, "E"], [1500, "F"]]
total_fare(taps)
Output
1475
Why
Day 0 pays 4 x 275 = 1,100, then 100 more to reach the 1,200 cap. Minute 1500 is a new day and pays 275.
Show approach and solution

Say this out loud

  • Ask what "within 120 minutes" means at exactly 120. Here it counts as a transfer.
  • Ask whether the ride that hits the cap earns a transfer. Here any ride with a charge does.
  • Keep money in integer cents; floating-point dollars drift.
  • The station is unused so far. Ask whether tapping again at the same station should count as a transfer.

Approach

Walk the taps once, tracking the minute of the last paid ride, whether its transfer is still open, the current day and how much was spent that day. A tap that qualifies for a transfer is free and closes the transfer. Otherwise the fare is 275, or less if the cap is close; any ride with a charge becomes the new paid ride and opens a transfer. Flags turn each part's rule on, so one function answers all three parts.

Python 3 solution
RIDE = 275
TRANSFER_WINDOW = 120
DAILY_CAP = 1200


def total_fare(taps, transfers=True, cap=True):
    total = 0
    last_paid = None
    transfer_open = False
    day = None
    spent_today = 0
    for minute, _station in taps:
        if minute // 1440 != day:
            day = minute // 1440
            spent_today = 0
        if transfers and transfer_open and minute - last_paid <= TRANSFER_WINDOW:
            transfer_open = False
            continue
        fare = min(RIDE, DAILY_CAP - spent_today) if cap else RIDE
        if fare > 0:
            total += fare
            spent_today += fare
            last_paid = minute
            transfer_open = True
    return total

Complexity: O(T) time for T taps, O(1) extra space.

What the interviewer will look for: An empty month, a tap exactly 120 minutes after a paid ride, two quick rides after one paid ride (only one is free), the ride that crosses the cap, and taps on both sides of midnight.

Problem 3MediumHash map of last positions

Playlist repeat rule

def follows_rule(plays, k), def first_violation(plays, k), def longest_fresh_stretch(plays)

A music app logs the song ids it plays, in order. The app promises that the same song never appears twice among any k consecutive plays.

  1. Part 1. Write follows_rule(plays, k), which returns True if the log keeps the promise.
  2. Part 2. Now write first_violation(plays, k), which returns the index of the first play that breaks the promise, or -1.
  3. Part 3. Now write longest_fresh_stretch(plays), the length of the longest run of consecutive plays with no song repeated at all.

Examples

Input
plays = [1, 2, 3, 1, 2, 2, 4]
follows_rule(plays, 3)
first_violation(plays, 3)
longest_fresh_stretch(plays)
Output
False
5
3
Why
Song 2 plays at indexes 4 and 5, inside one window of 3. Song 1 at indexes 0 and 3 is fine, because no 3 consecutive plays hold both.
Input
follows_rule([5, 6, 5], 2)
Output
True
Show approach and solution

Say this out loud

  • Pin down "within k plays" with an example before you code; the off-by-one is the whole problem.
  • A dict of each song's last index is enough. You never need to keep the whole window.
  • Part 3 is a sliding window: when a song repeats, move the start just past its earlier copy.

Approach

Two plays of the same song at indexes i and j share a window of k consecutive plays exactly when j - i < k. So remember the last index of each song and check the gap at every play. For Part 3, keep a window start and push it past the previous copy of any song that repeats inside the window.

Python 3 solution
def first_violation(plays, k):
    last_seen = {}
    for i, song in enumerate(plays):
        if song in last_seen and i - last_seen[song] < k:
            return i
        last_seen[song] = i
    return -1


def follows_rule(plays, k):
    return first_violation(plays, k) == -1


def longest_fresh_stretch(plays):
    last_seen = {}
    best = 0
    start = 0
    for i, song in enumerate(plays):
        if song in last_seen and last_seen[song] >= start:
            start = last_seen[song] + 1
        last_seen[song] = i
        best = max(best, i - start + 1)
    return best

Complexity: O(n) time for each function, O(distinct songs) space.

What the interviewer will look for: k = 1 (the promise always holds), an empty log, the same song back to back, and a repeat exactly k plays apart (allowed).

Problem 4MediumGrid simulation

Warehouse robot commands

def run(commands, shelves=()) -> tuple[list[int], int]

A robot starts at (0, 0) facing north on an unbounded warehouse floor, where north is +y and east is +x. It follows a string of commands: 'F' moves one cell forward, 'B' moves one cell backward without turning, and 'L' and 'R' turn 90 degrees left or right in place.

  1. Part 1. Return the robot's final [x, y].
  2. Part 2. Now some cells hold shelves, given as a list of [x, y]. A move into a shelf is skipped: the robot stays put and goes on to the next command.
  3. Part 3. Now also return how many distinct cells the robot visited, counting the start.

Examples

Input
run("FFRFFLB")
Output
([2, 1], 6)
Input
run("FFRFFLB", shelves=[[1, 2]])
Output
([0, 1], 3)
Why
Both moves east are blocked by the shelf at (1, 2), so the robot only backs up to (0, 1).
Show approach and solution

Say this out loud

  • Keep the four directions in a list so turning is just an index change, not a chain of if statements.
  • Store shelves as a set of tuples: lookups are O(1), and Python lists cannot go in a set.
  • Ask whether the start cell can hold a shelf, and whether moving backward is also blocked by shelves. Here it is.

Approach

Keep the position and a direction index into [north, east, south, west]. L and R change the index modulo 4. F and B compute the next cell one step forward or backward; if it is not a shelf, move there and add it to a set of visited cells. Return the position and the size of the set.

Python 3 solution
DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)]  # north, east, south, west


def run(commands, shelves=()):
    blocked = {tuple(cell) for cell in shelves}
    x, y, facing = 0, 0, 0
    visited = {(0, 0)}
    for command in commands:
        if command == 'L':
            facing = (facing - 1) % 4
        elif command == 'R':
            facing = (facing + 1) % 4
        elif command in ('F', 'B'):
            dx, dy = DIRECTIONS[facing]
            step = 1 if command == 'F' else -1
            nxt = (x + dx * step, y + dy * step)
            if nxt not in blocked:
                x, y = nxt
                visited.add(nxt)
    return [x, y], len(visited)

Complexity: O(C + S) time for C commands and S shelves, O(C + S) space.

What the interviewer will look for: An empty command string, only turns (the robot never moves), four left turns (back to north), backing into a shelf, and very long command strings.

Problem 5HardMonotonic deques

Sensor stability windows

def jumps(readings, t), def longest_stable(readings, w)

A temperature sensor sends one reading per minute, stored in the list readings.

  1. Part 1. Write jumps(readings, t), which returns the indexes where a reading differs from the previous one by more than t.
  2. Part 2. Write longest_stable(readings, w), the length of the longest run of consecutive minutes whose highest and lowest readings differ by at most w. It must run in O(n).
  3. Part 3. Now the sensor sometimes misses a minute, recorded as None. A stable run cannot include a missing minute, and jumps only compares two readings that are next to each other with no gap between them.

Examples

Input
longest_stable([4, 6, 5, 9, 8, 8, 7], 2)
Output
4
Why
[9, 8, 8, 7] has a high of 9 and a low of 7.
Input
readings = [20, 21, 25, 24, 23, None, 22, 22]
jumps(readings, 2)
longest_stable(readings, 2)
Output
[2]
3
Why
The None splits the readings, so 23 and 22 are never compared, and [25, 24, 23] is the longest stable run.
Show approach and solution

Say this out loud

  • Start by naming the brute force (check every window, O(n^2) or worse), then improve it.
  • Two monotonic deques keep the window's highest and lowest readings at their fronts.
  • Ask what a missing reading should mean before Part 3. Treating it as a hard break is the simplest honest choice.

Approach

jumps compares each reading with the one before it, skipping pairs that include None. longest_stable slides a window with two deques of indexes: one with readings in decreasing order (its front is the max) and one increasing (its front is the min). When max minus min passes w, move the start right and drop fronts that fall out. A None clears both deques and restarts the window after it.

Python 3 solution
from collections import deque


def jumps(readings, t):
    result = []
    for i in range(1, len(readings)):
        a, b = readings[i - 1], readings[i]
        if a is not None and b is not None and abs(b - a) > t:
            result.append(i)
    return result


def longest_stable(readings, w):
    highs, lows = deque(), deque()
    best = 0
    start = 0
    for i, value in enumerate(readings):
        if value is None:
            highs.clear()
            lows.clear()
            start = i + 1
            continue
        while highs and readings[highs[-1]] <= value:
            highs.pop()
        highs.append(i)
        while lows and readings[lows[-1]] >= value:
            lows.pop()
        lows.append(i)
        while readings[highs[0]] - readings[lows[0]] > w:
            start += 1
            if highs[0] < start:
                highs.popleft()
            if lows[0] < start:
                lows.popleft()
        best = max(best, i - start + 1)
    return best

Complexity: O(n) time, since each index enters and leaves each deque once. O(n) space in the worst case.

What the interviewer will look for: All readings equal, strictly increasing readings with w = 0 (answer 1), None at the start or end, every reading missing (answer 0), and long lists.

Problem 6HardBFS, then Dijkstra

Courier map

def reachable(grid), def shortest(grid, target), def fastest(grid, target)

A courier map is a list of strings: '.' is a street, '#' is a building, and 'S' is the depot. The courier moves up, down, left or right, one cell at a time.

  1. Part 1. Write reachable(grid), the number of cells the courier can reach from S, counting S.
  2. Part 2. Write shortest(grid, target), the fewest moves from S to the [row, col] target, or -1 if it cannot be reached.
  3. Part 3. Now some streets are slow: '~' cells take 2 minutes to enter instead of 1. Write fastest(grid, target), the fewest minutes from S to the target, or -1.

Example

Input
grid = ["S~~~.",
        ".....",
        "#.###",
        ".#..."]
reachable(grid)
shortest(grid, [0, 4])
fastest(grid, [0, 4])
shortest(grid, [3, 3])
Output
11
4
6
-1
Why
The top row is the fewest moves, but its three slow cells cost 7 minutes, so the bottom route of 6 moves is faster. The bottom-right corner is walled off.
Show approach and solution

Say this out loud

  • BFS gives the fewest moves only when every move costs the same. Once '~' costs 2, switch to Dijkstra.
  • Return as soon as the target comes off the heap; that is when its time is final.
  • Ask what to return if the target is a building or off the map. Here it is -1.

Approach

Parts 1 and 2 share one breadth-first search from S that records the number of moves to every reachable cell: Part 1 counts them and Part 2 looks up the target. Part 3 needs Dijkstra because entering a slow cell costs 2: pop the cheapest cell from a heap, relax its neighbors with the right cost, and stop when the target is popped.

Python 3 solution
import heapq
from collections import deque

MOVES = ((1, 0), (-1, 0), (0, 1), (0, -1))


def find_start(grid):
    for r, row in enumerate(grid):
        c = row.find('S')
        if c != -1:
            return r, c
    raise ValueError('no depot')


def neighbors(grid, r, c):
    for dr, dc in MOVES:
        nr, nc = r + dr, c + dc
        if 0 <= nr < len(grid) and 0 <= nc < len(grid[nr]) and grid[nr][nc] != '#':
            yield nr, nc


def bfs_moves(grid):
    start = find_start(grid)
    moves = {start: 0}
    queue = deque([start])
    while queue:
        r, c = queue.popleft()
        for cell in neighbors(grid, r, c):
            if cell not in moves:
                moves[cell] = moves[(r, c)] + 1
                queue.append(cell)
    return moves


def reachable(grid):
    return len(bfs_moves(grid))


def shortest(grid, target):
    return bfs_moves(grid).get(tuple(target), -1)


def fastest(grid, target):
    goal = tuple(target)
    start = find_start(grid)
    best = {start: 0}
    heap = [(0, start)]
    while heap:
        minutes, cell = heapq.heappop(heap)
        if cell == goal:
            return minutes
        if minutes > best[cell]:
            continue
        for nr, nc in neighbors(grid, *cell):
            cost = minutes + (2 if grid[nr][nc] == '~' else 1)
            if cost < best.get((nr, nc), float('inf')):
                best[(nr, nc)] = cost
                heapq.heappush(heap, (cost, (nr, nc)))
    return -1

Complexity: BFS is O(R x C) time and space. Dijkstra is O(R x C x log(R x C)) time.

What the interviewer will look for: The target is the depot (0), the target is a building, the depot is boxed in, a map of only slow streets, and a large open map.

Practice discussion questions

Karat's first part is about 10 minutes of discussion questions. Karat says senior candidates get more discussion and analysis, such as why a piece of code slows down as it scales. Answer each of these out loud in about 2 minutes, then compare with the model answer.

  • When would you pick a hash map over a sorted list?

    Show a model answer

    When I only need exact-key lookups, inserts and deletes: a hash map does each in O(1) on average. A sorted list or a balanced tree costs O(log n) per lookup but keeps order, so it wins for range queries, the nearest key, or walking keys in order.

  • This loop gets slow as the list grows. Why? for x in items: if x not in seen_list: seen_list.append(x)

    Show a model answer

    Checking membership in a list scans it, which is O(n), so the loop is O(n^2) overall. Making seen a set turns each check into O(1) on average and the loop into O(n). If the output order matters, keep the list for output and use the set only for the check.

  • What is the difference between a process and a thread?

    Show a model answer

    A process has its own memory; threads inside one process share it. Threads are cheaper to start and can share data directly, but that is also why they need locks to avoid race conditions. Processes are isolated, so one crashing does not take the others down.

  • How would you design an API endpoint that returns a user's orders?

    Show a model answer

    GET /users/{id}/orders, with filters and sorting as query parameters and pagination by cursor so results stay stable while new orders arrive. Return 404 for an unknown user, check that callers can only see their own orders, and add rate limits. For writes, I would talk about idempotency keys so retries do not create duplicates.

  • What does a database index do, and what does it cost?

    Show a model answer

    An index, usually a B-tree, lets the database find rows by a column without scanning the whole table, roughly O(log n) instead of O(n). It costs disk space and slows every insert, update and delete, because the index has to be updated too. Index the columns you filter and join on, not everything.

  • How would you test a function that parses dates?

    Show a model answer

    Normal dates first, then boundaries: leap days, month ends, the year change and time zones. Then bad input, like empty strings, impossible dates and the wrong format. I would make them unit tests, add a round-trip test (format, then parse back), and add a test for every bug found later.

How to practice for the Karat round

  1. Step 1

    Rehearse the full hour

    Use the 60-minute plan above: discussion questions first, then one problem from start to Part 3. Practicing the real shape of the hour matters as much as the code.

  2. Step 2

    Talk the whole time

    Karat interviews are live with an engineer, and the recording goes to the company. Restate each part, say your plan, then code, so your reasoning is on the record.

  3. Step 3

    Finish each part before the next

    A working Part 1 beats a half-written Part 3. Karat says it is not a race, so keep a steady pace and do not get stuck on one approach for long.

  4. Step 4

    Know the redo rule

    If a session goes badly, you can ask for a redo within 24 hours and get new questions. The company sees both results, so treat the first one as the real thing.

OphyAI Desktop for Mac and Windows

Practice with these, then use Coding Copilot in your Karat interview

Coding Copilot in the OphyAI desktop app reads the coding problem on your screen with one hotkey and returns an approach, complexity notes and working code in your language.

Practice with these, then bring Coding Copilot into the real Karat interview. While you practice, press Cmd/Ctrl + Shift + A to snap the problem window, or Cmd/Ctrl + Shift + S to drag a box around one part, and Coding Copilot returns an answer card: how it read the problem, what to say first, the code with line numbers, a dry run with edge cases, and the Big O.

In a live Karat interview, press Space or Cmd/Ctrl + Shift + L and it transcribes each part the Interview Engineer asks out loud. Follow-ups remember the problem, so Part 2 updates the same code instead of starting over. The card shows in a floating overlay that macOS and Windows leave out of screen sharing and recording. It stays on top of a full-screen editor or browser and opens without taking focus from your editor.

  • Cmd/Ctrl + Shift + A snaps the problem window, even when other windows cover it, and Cmd/Ctrl + Shift + S lets you drag a box around part of the screen. In a live interview, Space or Cmd/Ctrl + Shift + L transcribes the question the interviewer asks out loud.

  • Answers show as a card in a floating overlay that macOS and Windows leave out of screen sharing and recording. It stays on top of a full-screen editor or browser and opens without taking the keyboard from your editor.

  • Each answer card shows how OphyAI read the problem, what to say first, the code with line numbers, a dry run with edge cases, and the Big O. Follow-ups remember the problem, so "now handle duplicates" updates the same code.

Free download. Coding Interview is on the Premium plan ($59 a month, 500 credits) and uses 15 credits per session. See everything it does

What it is hidden from, and what it is not

Every Karat interview is recorded with audio and a code replay, and interviewers watch for signs of outside help, such as looking at another screen. Some interview types include Karat's built-in AI assistant.

The OphyAI window is hidden from screen sharing and recording, but not from a webcam, a person watching, or the platform's own keystroke, paste and tab tracking.

Karat interview questions FAQ

About 10 minutes of discussion questions, then about 40 minutes of coding. Karat says newer grads get classic algorithms and data structures questions, while senior candidates get more discussion and analysis, such as why code slows down as it scales.

No. Every problem here is original. Karat's Interview Engineer picks the questions for the role, Karat tries not to show you the same content twice, and a redo comes with new questions, so practice the patterns rather than specific questions.

Do timed, hour-long runs out loud: a short intro, about 10 minutes of quick concept answers, then about 40 minutes on one multi-part problem from this page. Also read the prep email Karat sends after you schedule.

The one you are fastest in, as long as the role allows it. Karat supports over 100 languages and frameworks. The solutions here use Python 3 because it keeps the code short.

Karat says the most important thing it evaluates is how successfully your code solves the problem, and that doing well is not like winning a race. Finish each part cleanly and keep moving rather than getting stuck.

Karat's Brilliant Black Minds program offers free live mock interviews 24/7 with feedback, aimed at Black software engineers. Otherwise, ask a friend to play the interviewer and read you these problems one part at a time.

Coding Copilot in the OphyAI desktop app transcribes the question the Interview Engineer asks, reads the problem on screen with one hotkey, and shows an approach and working code in an overlay hidden from screen sharing. Karat records every interview, and interviewers watch for signs of outside help, such as looking at another screen.

Practice here, then take Coding Copilot into your Karat interview

Download OphyAI Desktop and bring Coding Copilot into your next coding interview: it hears the question, reads the problem, and shows the approach and code in an overlay your screen share leaves out.