Interview guides · Checkr
Checkr Software Engineer Interview Guide
Checkr Software Engineer interview guide: the three reported rounds, coding, refactoring, design and behavioral questions, and a seven-day prep plan.
The 60-second brief
Checkr builds background check technology. Candidates describe the Software Engineer role as work on the data platform behind it: API integrations, background check workflows and developer tooling, with features owned from design through testing and deployment. The requirements candidates describe name Ruby, Python or GoLang as example languages, plus REST APIs and microservices. Observability frameworks, cloud infrastructure (AWS or GCP) and experience improving developer workflows are listed as nice-to-have. Candidates also report a hybrid working model, with hubs in San Francisco, Denver and Santiago.
This guide is for software engineers preparing for the Checkr loop, at any level. It covers the three reported rounds, the coding and refactoring questions (name matching, parsing API responses, complexity tuning, building a REST endpoint), the object-oriented and system design questions (a background check schema, a class model, vendor integrations, monolith versus microservices, observability) and the behavioral deep-dive. Candidates report that design depth rises for Senior and Staff roles, so scale the design practice to your level.
Candidates report three rounds over roughly three to five weeks: a recruiter screen, a technical screening that is either a coding challenge or a deep dive into past projects, and a virtual onsite made of several one-hour sessions.
Focus areas
- Code Refactoring
- Object-Oriented Design (OOD)
- Architecture Design
Overview
Candidates describe the day-to-day as writing production code, joining architectural design reviews, working with product managers, operations staff and other engineering teams, and owning a feature from design to deployment. They also mention compliance standards and background check workflows. For preparation, that means two skills matter equally: writing small, correct, well-tested code, and explaining why a design looks the way it does.
The reported questions fall into three groups. The coding and refactoring group is practical: a name-matching algorithm for variations in user input, refactoring a legacy snippet for readability and performance, parsing and validating API response formats, optimizing code with poor time complexity, and building a simple REST API endpoint from scratch. The object-oriented and system design group covers a database schema for a background check feature, classes and interfaces for a board game or business process, a system that integrates several third-party vendors for low-latency retrieval, monolith versus microservices, and an observability framework for distributed services. The behavioral group is a project deep-dive, stakeholder disagreement, mentoring or workflow improvement, interest in the mission, and constructive feedback.
Candidates describe the sessions as closer to pair programming than to an exam, with open-ended prompts where you state your assumptions and ask questions. Pick one language from the reported list (Ruby, Python or GoLang) and do all your practice in it, so that idioms, the standard library and a test runner are second nature. For design practice, draw the diagram, name the entities, and say what each choice costs. Keep the schema, API and observability answers connected: a vendor-integration design should mention timeouts and retries, a schema should mention history and constraints, and an observability answer should say what you would alert on.
The Checkr interview process
Candidates describe the three stages below; they also report that the focus of each stage shifts with the seniority of the role and the team's current needs.
Recruiter Screen
The first stage is a recruiter conversation about your background and your interest in the company. Candidates report no technical exercise here, but the stage still sets up the rest of the loop: your language choice, the level you are interviewing at and any location constraints come out in it. Candidates also report a hybrid working model with hubs in San Francisco, Denver and Santiago, so expect to talk about working in person. Prepare a short account of your career that ends at this role, and a specific reason for the background check and compliance space that you can repeat later in the loop.
What to demonstrate
- How your background maps to the software engineering role
- Your interest in the company and in background check technology
- Practical fit, including the hybrid and location arrangement
How to prepare
- Write a short career narrative that ends with why background checks and compliance work interests you, using a project of your own as the link.
- Decide which of Ruby, Python or GoLang you will interview in, and be ready to say how recent your production experience in it is.
- Prepare questions about the team, the stack and what the technical screening will look like for your level.
- Have your location and hybrid-work constraints ready so they are settled early.
Technical Screening
Candidates report that the technical screening is either a coding challenge or a deep dive into your past projects. Prepare for both. For the coding branch, string manipulation, data structures, parsing and complexity tuning are the practice areas that match the reported coding questions, and candidates describe talking through the code while writing it. For the project branch, pick one system you built, separate your own contribution from the team's, and prepare the reasons behind each architectural choice, because candidates report follow-ups on the why.
What to demonstrate
- Working, idiomatic code in your chosen language, with edge cases handled
- How clearly you explain your approach while coding
- Your own technical contribution and reasoning on a past project
How to prepare
- Practise string-matching and parsing problems in your chosen language with unit tests, including empty, malformed and unusual inputs.
- Take an inefficient loop-inside-a-loop solution, state its complexity, and rewrite it with a hash map, sorting or a set, then state the new complexity.
- Prepare one project story with the options you rejected, the numbers you can defend, and what you would change now.
- Rehearse thinking aloud: restate the problem, list assumptions, then code.
Virtual Onsite
The virtual onsite is described as several one-hour sessions spanning coding, system design, refactoring and behavioral conversation. Candidates report diagrams and whiteboard-style design discussions, with entities, data structures and trade-offs justified aloud. The refactoring session is reported to center on spotting code smells and improving structure without breaking behavior, using SOLID and DRY ideas and a way to verify the change. Candidates also report open-ended prompts, so state assumptions rather than waiting for complete requirements. Prepare each session type separately, then rehearse them back to back.
What to demonstrate
- Clean, testable code and safe refactoring that preserves behavior
- Object-oriented modelling and system design with explicit trade-offs
- Data modelling that maps to the business requirement
- Behavioral answers that show your own decisions and impact
How to prepare
- Refactor a messy function from your own code: write characterization tests first, then change structure in small, verifiable steps and name each smell.
- Practise a schema design and a class-model design on a whiteboard or paper, covering entities, relationships, constraints and extension points.
- Outline one design each for multi-vendor data retrieval, the monolith versus microservices choice, and observability across services.
- Write story outlines for the five reported behavioral prompts and say them aloud.
Where candidates lose points
Treating name matching as an exact string comparison, or patching one variation at a time
List the variations first: case, whitespace, punctuation, accents, nicknames, middle names, word order and typos. Normalize, then compare with an edit-distance style measure, and say what a false match costs against a missed match, since the data here describes people.
Refactoring the legacy snippet by rewriting it before pinning down what it does
Read it, write tests that capture current behavior, then change structure in small steps, naming each smell and the principle behind the fix. Show how you verify performance changes separately from readability changes.
Parsing and endpoint code that only handles the happy path
Test malformed JSON, missing fields, wrong types, unexpected status codes and timeouts. Return precise errors rather than a generic failure, and say which cases you would add with more time.
Answering design prompts with a diagram but no trade-offs, such as picking microservices or a schema without saying what it costs
Tie every choice to a requirement. For the vendor-integration prompt, cover timeouts, retries, fallbacks and caching; for a schema, cover constraints, history and indexes; for observability, say what you would alert on.
Coding or designing in silence, or waiting for complete requirements
Candidates describe open-ended prompts and a pair-programming style. Ask clarifying questions, state your assumptions out loud and adjust when the interviewer redirects.
Questions & practice
Implement a name-matching algorithm to handle variations in user input.
Medium · Coding and Refactoring
Approach
- Clarify what counts as a match first: same person despite case, accents, punctuation, word order ("Smith, John" vs "John Smith"), nicknames (Bob/Robert), initials and typos. Also ask whether the output is a yes/no or a ranked score with a threshold.
- Normalize before comparing: Unicode NFKD, drop combining marks (unicodedata.combining in Python), casefold, strip punctuation, collapse whitespace, drop suffixes like Jr./III, then tokenize so token order can be ignored or aligned.
- Score token pairs with Levenshtein edit distance (DP, O(m*n) time, O(min(m,n)) space with two rows) or Damerau-Levenshtein for transpositions. Jaro-Winkler suits short names because it rewards a shared prefix. Add a nickname map and an initial-matches-full-name rule.
- Combine per-token scores (first, middle, last weighted differently) into one score and compare it to a tunable threshold. Keep exact-match fast paths so the expensive DP runs only when needed.
- Edge cases: empty or missing middle name, hyphenated or two-part surnames, single-token names, non-Latin scripts that normalization cannot transliterate, and very long input. Write a table of these as unit tests.
Follow-up
- A false match and a missed match have different costs here. How would you set the threshold, and which error would you rather make?
- How would you match one name against millions of records without comparing every pair (blocking keys, trigram index, BK-tree)?
- How would you add phonetic matching such as Soundex or Double Metaphone, and where does it go wrong?
Refactor a provided snippet of legacy code to improve readability and performance.
Medium · Coding and Refactoring
Approach
- Read the code end to end and say what it does before touching it. Then write characterization tests that pin current outputs, including odd ones, so every later step can be checked against them.
- Refactor in small behaviour-preserving steps, running tests after each: rename unclear variables, extract functions with one job, replace nested conditionals with guard clauses, name magic numbers, and remove duplicated branches (DRY).
- Separate side effects (I/O, DB calls, time, randomness) from pure logic so the logic can be unit-tested and the dependencies passed in or mocked.
- Treat performance as its own step: find the hot path (for example
x in listinside a loop, O(n*m)), swap in a set or dict for O(n+m), and state the before and after complexity. - If you find a bug, say so and keep it out of the refactor commit. Fix it separately so a reviewer can tell a structural change from a behaviour change.
Follow-up
- How do you prove the refactored code behaves the same as the original?
- If you could ship only one of these changes today, which one and why?
- When would you leave legacy code alone rather than refactor it?
Optimize a piece of code that is currently suffering from inefficient time complexity.
Medium · Coding and Refactoring
Approach
- Find the dominant cost before changing anything: read the loops, state the current complexity, and measure with a profiler (cProfile in Python, pprof in Go) on a realistic input size.
- Apply the matching fix: a nested-loop lookup becomes a hash map or set (O(n^2) to O(n) expected), a sort inside a loop moves outside it, repeated range sums become prefix sums, repeated subproblems get memoized, and per-row database calls (N+1) become one batched query.
- Watch hidden costs: list.pop(0) and
in listare O(n), building a string with += in a loop can be quadratic (use ''.join), and copying slices inside recursion adds O(n) per call. - State the trade-off: most speedups spend memory (a hash set costs O(n) extra space) or readability, so say why it is worth it at the input sizes given.
- Prove equivalence: keep the slow version as a reference and compare outputs on random and edge inputs (empty, single element, duplicates, already sorted) before deleting it.
Follow-up
- What if the input is too big for the extra hash map to fit in memory?
- Does your answer change if the input is already sorted or nearly sorted?
- How would you stop the slow pattern from coming back in a later change?
Hold a per-tenant active cap against concurrent creates
Hard · write skew · isolation levels · constraints
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
Write a function to parse and validate specific API response formats.
Medium · Coding and Refactoring
Approach
- Pin the contract first: required and optional fields, types, allowed enum values, nesting, and what null means compared with a missing key. Check the HTTP status and the error envelope before parsing the success body.
- Parse defensively. In Python, json.loads raises json.JSONDecodeError (a ValueError subclass) on malformed input. Then validate into a typed object (dataclass with explicit checks, or a pydantic model) so the rest of the code never handles raw dicts.
- Decide between fail-fast and collecting every error. For API responses I collect errors with a field path (for example data.items[2].status) so one log line explains everything that was wrong.
- Be strict on what you use and tolerant on what you don't: ignore unknown fields for forward compatibility, but reject unknown enum values or map them to an explicit UNKNOWN. Parse ISO 8601 timestamps with timezone and treat numbers sent as strings deliberately.
- Cost is linear in payload size. Test with an empty list, missing required field, wrong type, extra field, truncated JSON, and a very large payload.
Follow-up
- The provider adds a new status value without notice. What does your parser do?
- How would you handle a response too large to load into memory at once (streaming parser)?
- Where should a validation failure go: exception, error result, log, metric, or all of them?
Build a simple REST API endpoint from scratch using your preferred framework.
Medium · Coding and Refactoring
Approach
- Pick a framework you can write quickly and say why (Flask or FastAPI in Python, Rails or Sinatra in Ruby, net/http or a small router in Go). Name the resource and routes, for example POST /candidates and GET /candidates/{id}.
- Validate input at the boundary and return the right status codes: 201 Created with a Location header on create, 400 or 422 for invalid input with a field-level error body, 404 for a missing id, 409 for a conflict such as a duplicate.
- Keep layers apart: the handler parses and validates, a service function holds the logic, and a repository handles storage. An in-memory dict behind that interface is fine at first and can be swapped for a database.
- Make retries safe: GET, PUT and DELETE are idempotent by definition. For POST, accept an Idempotency-Key header and return the stored response on a repeat.
- For list endpoints, add cursor pagination and a limit. Write tests for the happy path, a validation failure and the not-found case, run with the framework's test client.
Follow-up
- How would you add authentication and per-client rate limiting to this endpoint?
- A client times out and retries a POST. How do you prevent a duplicate record?
- How would you version this API once external clients depend on it?
Design a database schema for a new background check feature.
Medium · Object-Oriented and System Design
Approach
- Ask what the feature does and who reads the data before drawing tables. Then name the entities. A typical starting point: account (the customer ordering checks), candidate, report (one order for one candidate), screening (each check inside a report), and a consent record.
- Write keys and relationships: report has foreign keys to account and candidate, and screening has a foreign key to report with a type and a status. Use a CHECK constraint or lookup table for status values, plus created_at and updated_at.
- Keep history as an append-only status_event table (screening_id, from_status, to_status, actor, at) instead of overwriting a status column. This gives an audit trail and makes 'how long in each state' queries possible.
- Index for the real queries: (account_id, created_at) for listing a customer's reports, (status, updated_at) for finding stuck work. Say what each index costs on writes.
- Treat personal data deliberately: encrypt sensitive columns, keep them in a separate table with tighter access, and plan retention and deletion. Handle re-runs and partial results without duplicating rows.
Follow-up
- How would you add a new screening type with different result fields: new table, JSONB column, or a generic key-value table?
- Write the query for reports where any screening has been pending longer than N days, and say which index serves it.
- A deletion request arrives for one candidate. What do you delete, what do you keep, and how?
Model the classes and interfaces required for a simplified board game or specific business process.
Medium · Object-Oriented and System Design
Approach
- Pin the rules and scope first (board size, players, win condition, or the process states and who triggers each transition), then list nouns as candidate classes and verbs as methods.
- For a board game: Game owns turn order and status, Board owns cells and bounds checks, Player is an interface (human or computer), and Move is a value object. Rule checks live in one place so Board stays a dumb container.
- Make the win check cheap. For n x n tic-tac-toe, keep counters per row, column and the two diagonals (+1 for one player, -1 for the other) so each move is checked in O(1) instead of rescanning the board.
- For a business process, model an explicit state machine: an enum of states, a transition table, and a method that rejects illegal transitions with a clear error. Side effects hang off transitions, not off scattered if statements.
- Justify the structure with SOLID: composition over inheritance, interfaces where a second implementation is likely (Strategy for rules or AI players), and encapsulated state that callers cannot set directly.
Follow-up
- How would you add undo and redo (Command pattern with a move history stack)?
- How would you add a new piece type or process step without editing existing classes?
- What changes if two players act at the same time over the network?
How would you design a system that integrates with multiple third-party vendors for low-latency data retrieval
Medium · Object-Oriented and System Design
How would you design a system that integrates with multiple third-party vendors for low-latency data retrieval?
Approach
- Clarify scale, the latency target, and whether callers need every vendor's answer or the first good one. Then put each vendor behind an adapter that implements one interface and normalizes its response into an internal model.
- Call vendors in parallel with a per-vendor timeout, so total latency is bounded by the slowest allowed call rather than the sum. Consider hedged requests or a fallback vendor for the slowest calls.
- Protect against failure: a circuit breaker per vendor, retries with exponential backoff and jitter only for idempotent calls, and per-vendor rate limiting (token bucket) so you stay under their quotas.
- Cache where freshness allows (TTL per data type) and move inherently slow vendors to an async path: accept the request, return a status resource, and finish via vendor webhook or a polling worker on a queue.
- Store raw vendor responses for audit and reprocessing, and emit per-vendor latency percentiles, error rate and breaker state so a degrading vendor is visible before callers notice.
Follow-up
- One vendor is down for an hour. What do callers see, and how do queued requests recover afterwards?
- Two vendors return conflicting data for the same subject. How do you resolve it?
- Your peak traffic exceeds a vendor's rate limit. What do you do?
Discuss the trade-offs between a monolithic architecture and a microservices-based approach for our platform.
Medium · Object-Oriented and System Design
Approach
- Frame it as a decision with criteria, not a preference: team count and ownership, how stable the domain boundaries are, whether parts of the system scale differently, and how often teams need to deploy independently.
- Monolith strengths: one deploy, in-process calls, ACID transactions across modules, simple local debugging. Costs: coupled releases, scaling everything together, and contention as more teams share one codebase.
- Microservices strengths: independent deploy and scaling, fault isolation, clear ownership. Costs: network latency and partial failure, no cross-service transactions (sagas, transactional outbox), duplicated data, heavier ops, and a need for tracing and contract testing.
- Name the middle path: a modular monolith with enforced module boundaries, then extract a service only where a boundary is proven, using the strangler fig pattern behind a routing layer.
- Give a concrete recommendation for the scenario given and say what evidence would change it, such as one component needing very different scaling or deploy cadence.
Follow-up
- Which part of a monolith would you extract first, and how would you migrate its data?
- How do you keep data consistent across services without distributed transactions?
- When would you merge two services back together?
How would you design an observability framework to track performance across our distributed services?
Medium · Object-Oriented and System Design
Approach
- Cover the three signals and how they link: metrics for trends and alerts, traces for one request's path across services, and structured logs for detail, all tagged with the same trace id so you can jump between them.
- Standardize per-service metrics with RED (rate, errors, duration) and resource metrics with USE (utilization, saturation, errors). Record latency as histograms: percentiles cannot be averaged across instances, but histogram buckets can be summed and then queried.
- Propagate trace context on every hop (W3C traceparent header, including through queues) using an OpenTelemetry SDK and a collector, so instrumentation is vendor-neutral and teams adopt one library.
- Control cost and cardinality: never use user or request ids as metric labels, sample traces (head-based for volume, tail-based to keep errors and slow requests), and set log retention per level.
- Alert on symptoms against SLOs, using error-budget burn-rate alerts rather than raw CPU thresholds, and give each alert a dashboard and runbook link.
Follow-up
- p99 latency jumped in one service while p50 stayed flat. Walk through how you find the cause.
- Tracing every request costs too much at your volume. What do you sample and what do you lose?
- How would you get many teams to adopt this without each building their own setup?
p99 jumped on one listing filter while p50 stayed flat
Medium · query planning · composite index · percentiles
After a release that added an owner_user_id filter to the resource listing, p99 rose from 90 ms to 1.9 s while p50 stayed at 40 ms. Traffic and row counts are unchanged. resource carries the index (tenant_id, status, updated_at DESC, resource_id DESC). The new query filters tenant_id and owner_user_id, orders by updated_at DESC, resource_id DESC, and takes 20 rows. On PostgreSQL, explain the shape of the regression, prove it from a query plan, and give the index you would add.
Approach
- Start from the shape. A flat p50 with a moved p99 means a subset of requests changed cost, not all of them, so the first job is naming the subset. Bucket the endpoint's latency by the tenant's row count; the natural hypothesis is that large tenants are a small share of requests and all of the tail.
- Get the plan for the new query on a large tenant with EXPLAIN (ANALYZE, BUFFERS). Expect an index scan over the tenant's range, a filter discarding most of it, then a Sort feeding the Limit, possibly reporting Sort Method: external merge Disk. Read actual rows on the scan node, not estimated.
- Explain why the existing index cannot serve it. A composite B-tree is seekable only as a left prefix, and with no equality predicate on status the scan cannot treat updated_at as an ordering, because rows in the tenant's range are ordered by status first. Everything matching must be read and sorted before LIMIT 20 can apply, so a tenant with 400,000 rows pays 400,000 rows to return 20.
- Add (tenant_id, owner_user_id, updated_at DESC, resource_id DESC). Equality on the first two columns leaves the index ordered by updated_at within that pair, so the plan becomes an index scan that stops after 20 rows with no Sort node. PostgreSQL can scan a B-tree backwards, so the DESC markers matter only if the two sort columns ever disagree in direction; keeping them explicit documents the order the keyset cursor depends on.
Follow-up
- The endpoint paginates with OFFSET. What does page 500 cost with your index, and what does the keyset version cost?
- How would you have caught this before release, given that a 10,000-row seed database produces the same plan shape at an unnoticeable cost?
Suggested practice checklist
The plan follows the three reported rounds: a first day to set up and map the loop, then coding, refactoring, API, schema and architecture practice, and finally behavioral stories and a full mock.
Day 1 — Map the loop and choose your language
- Write down the three reported rounds and, beside each, the question types it covers: coding, refactoring, design and behavioral.
- Choose Ruby, Python or GoLang for all coding practice and set up a test runner and a blank project.
- Rate your confidence from one to five on each of the fifteen reported questions and mark the lowest five.
Deliverable: A one-page loop map, a ready project with a working test command, and a confidence list of the reported questions.
Day 2 — Name matching and complexity tuning
- Implement a name matcher in your chosen language that normalizes input and scores candidates with edit distance, then write tests for case, accents, nicknames, reordered names and typos.
- Write a deliberately slow quadratic version of a matching or deduplication task, then rewrite it with a hash map or by grouping candidates first, and state both complexities.
Deliverable: A tested name-matching function with a variations table, plus a before and after complexity note for the optimized version.
Day 3 — Refactor a legacy snippet
- Pick a long function with nested conditionals from your own code or an open-source project and write characterization tests for it.
- Refactor in small steps, naming each smell and the SOLID or DRY idea behind the fix, and run the tests after every step.
- Write a short summary of what got more readable, what got faster and how you know.
Deliverable: A before and after diff with passing tests and a summary of each smell removed.
Day 4 — REST endpoint and response validation
- Build a small REST endpoint from scratch in the framework you know best for your language, with request validation and meaningful status codes.
- Write a function that parses and validates a third-party API response, covering missing fields, wrong types, error bodies and a timeout, with a test for each case.
Deliverable: A small running service with tests, and a response validator with one test per failure mode.
Day 5 — Schema and object-oriented design
- Design a database schema for a background check feature: list entities, relationships, status transitions, constraints, indexes and an audit history, and draw it.
- Model classes and interfaces for a board game, then for a business process of your choice, and explain how a new rule would be added.
Deliverable: A schema diagram, two class diagrams with interfaces, and a solved concurrency drill.
Day 6 — Vendors, architecture and observability
- Outline a design that pulls data from several third-party vendors with low latency: parallel calls, timeouts, retries, fallbacks, caching and what you return when a vendor fails.
- Write a one-page argument for and against monolith versus microservices, ending with the situation in which you would pick each.
Deliverable: Three one-page design outlines (vendors, architecture trade-offs, observability) and a written debugging sequence for the latency drill.
Day 7 — Behavioral stories and full mock
- Write a story outline for each of the five reported behavioral prompts, naming your own decisions and one measurable result in each.
- Say each story aloud and trim it until the decision and its reasoning come first.
- Run a mock with a partner in the loop's order: a coding problem, a design prompt, then two behavioral prompts, narrating your thinking throughout.
Deliverable: Five story outlines and a recorded or noted mock session with a list of weak spots to fix.
Project deep-dive and behavioral prep
Candidates report that the behavioral stage covers your past impact, your technical decision-making and your fit with company values, and that project questions focus on what you personally contributed and why you made your architectural choices. Prepare stories that show your own decisions, a trade-off you can defend and a result you can explain, and rehearse the answer on why you want to work in background checks and compliance.
- Tell me about a design trade-off you had to explain to a non-technical stakeholder.
Walk me through a challenging technical project from your resume and the trade-offs you faced.
Medium · Behavioral and Experience Deep-Dive
Approach
- Choose a project where you personally made a named decision, such as a schema, a service boundary, a migration or a caching choice. Say "I" for your parts and name the teammates for theirs, since candidates report being asked to separate your contribution from the team's.
- Give the context in two sentences, then spend most of the time on the options: at least two alternatives, the criterion you chose by (latency, data integrity, maintenance cost, team size) and what you gave up.
- Quantify only what you can defend: before and after latency, error rate, incident or support volume. If you have no numbers, describe how you would have measured.
- Prepare the "why" behind each architectural choice, since candidates report follow-ups on exactly that. A story that lists technologies without a decision does not hold up.
- End with the tests, monitoring or rollout step that told you it worked, and one thing you would do differently.
Follow-up
- Why did you pick that option over the alternative you rejected?
- How did you verify the change did not break existing behavior?
- What would you change if you rebuilt it with today's knowledge?
How do you handle disagreements with stakeholders regarding technical priorities?
Medium · Behavioral and Experience Deep-Dive
Approach
- Pick a real disagreement where the other side had a legitimate goal, for example a product deadline against reliability work. Show that you understood their constraint before you argued yours.
- Translate the technical concern into their terms: customer impact, delivery risk, compliance exposure or support load. The requirements candidates describe include explaining technical trade-offs to non-technical stakeholders, so make this step visible.
- Bring evidence and options, not a veto: incident history, an estimate, a prototype, or a smaller scope that delivered part of the value with part of the risk.
- State the outcome honestly, including a case where you did not get your way and committed anyway, and what happened afterward.
- The story fails when you were simply right and the stakeholder was simply wrong, or when you gave in without ever raising the concern.
Follow-up
- What if the stakeholder still disagreed after you showed the data?
- What did you give up, and was it the right trade?
- How did the working relationship look afterward?
Describe a time you had to mentor a peer or improve a team’s developer workflow.
Medium · Behavioral and Experience Deep-Dive
Approach
- Choose one concrete case: onboarding a newer engineer to a codebase, a review checklist, removing flaky tests, speeding up CI, or writing a runbook. Vague claims about being a good mentor carry no weight.
- Show the diagnosis first: what was slowing people down and how you knew (review turnaround, repeated bug class, CI failure rate).
- Describe the mechanism you built or practised, such as pairing sessions, a template, a script or a lint rule, and how you got adoption without formal authority.
- Give the result: a measurable change, or what the mentee did independently afterward. Add one thing that did not work and how you adjusted.
- Tie it to the code-quality themes candidates report, such as readability, testing and review habits, so the story fits the role.
Follow-up
- How did you handle a teammate who resisted the change?
- How did you know the workflow change actually helped?
- How would you scale this approach to a larger team?
What interests you about our mission, and how does your background contribute to that?
Medium · Behavioral and Experience Deep-Dive
Approach
- Candidates report being asked why you want to work in the background check and compliance space, so a generic mission statement will not do. Open with one honest reason tied to something you have built or cared about, such as record accuracy, matching data to the right person, or systems where an error affects an individual.
- Connect that reason to the problem types in this loop: name matching, vendor integrations, schema design and observability. Say which of those you would want to work on and why.
- Show you have thought about the other side: background check data is personal, and mistakes have consequences for individuals, so accuracy, privacy and error handling matter to an engineer. Do not claim to know internal company practices.
- Map two or three of your skills to the role as candidates describe it, for example REST APIs, microservices, observability or improving developer workflow.
- Read the public product pages beforehand and name one thing you learned. Repeating slogans, or an answer that would fit any company, is the usual failure.
Follow-up
- Why this space rather than another data-heavy product?
- What concerns you about working on background check software?
- Which part of the role would you want to own first?
Tell me about a time you received constructive feedback and how you implemented it.
Medium · Behavioral and Experience Deep-Dive
Approach
- Choose feedback that was specific and about your work, such as a pattern in your code reviews, unclear design documents or communication during an incident. A weakness disguised as a strength is easy to spot.
- Show your first reaction: you asked a clarifying question, restated what you heard and did not defend yourself. Then say whether you fully agreed.
- Describe concrete changes with an artifact or habit behind them, such as a pre-review checklist, a rewritten design doc template or smaller pull requests, and evidence it stuck, like later review comments or a follow-up from the same person.
- Close the loop by saying you asked for further feedback afterward, and what you now do differently.
- The answer fails when the feedback is trivial, when the story blames the giver, or when the implementation is just a vague intention to improve.
Follow-up
- Did you agree with the feedback at the time?
- What was the hardest piece of feedback to act on?
- How do you give feedback to a peer yourself?