Interview guides · Cognite
Cognite Software Engineer Interview Guide
Cognite Software Engineer interview guide: six reported rounds, coding and system design questions, behavioral prompts and a seven-day prep plan.
The 60-second brief
Candidate reports describe Cognite as an industrial data company: its software helps large enterprises work with the data produced by heavy-industry operations. Engineers in this role are described as building features that touch data pipelines, user-facing interfaces and services that must stay up under heavy load, working alongside Data Scientists, Product Owners and other engineers. Core infrastructure, API development and front-end work are all mentioned as possible areas, so the exact team decides the mix.
This guide is for candidates preparing for a Software Engineer loop at Cognite. It covers the six reported rounds, nine technical questions (stream filtering, a classic data structure problem, algorithm optimization, refactoring, industrial data ingestion, IoT sensor updates, database trade-offs, maintainable application structure, and an architectural-mistake story) and three behavioral prompts, plus a plan that ties them together. It does not assume a specific team or seniority, because reports say the questions vary by both.
Candidates report six rounds over roughly 4-6 weeks: an initial screening, a remote coding session, a system design discussion, a behavioral interview, conversations with peers and leadership, and a final decision. Pace and structure are said to shift with the team and the urgency of the role.
Focus areas
- System Design
- Data Structures
- Algorithms
Overview
The role as candidates describe it mixes three kinds of work: writing correct, readable code; building services that keep working as data volume and feature count grow; and collaborating with Data Scientists and Product Owners. The reported questions mirror that mix. Four are coding questions (stream filtering, a classic linked-list, tree or hash-map problem, optimizing an algorithm for high-volume input, refactoring with modern language features such as functional streams). Four are design questions (industrial data ingestion, frequent updates from thousands of IoT sensors, database architecture trade-offs, keeping an application maintainable). Four are behavioral.
For the coding side, the listed requirements name Python, Java, Go and JavaScript or TypeScript as examples of a modern language, plus Git and testing. Pick one language and become fluent in its standard library, its iterator or stream facilities and its test runner, because the reported refactoring question is explicitly about modern language features. Practise in a plain online editor, and say the complexity of every solution aloud. A reported topic list also includes live coding, data structures, algorithms and ReactJS, so if your target team builds front-end interfaces, keep a component-state exercise in your rotation.
For the design side, the data-heavy framing matters. Ingestion and sensor-update questions are about write volume, ordering, duplicates, late data, backpressure and what happens when a downstream store is slow or down. Reported evaluation areas for design are scalability, latency versus throughput and data consistency, with an outage in a microservice and a notification service as example scenarios. Practise stating the numbers you assume, naming the bottleneck, and then choosing a storage model on purpose. The database trade-off question rewards a clear comparison along access pattern, consistency and operational cost rather than a product name.
The behavioral rounds are a standard part of the loop according to candidate reports, and two of the four reported prompts are about technical judgement: a disagreement and an architectural mistake. Prepare stories where you made a decision, show the evidence you used and the outcome. Candidates are also advised to be ready to explain the reasoning behind any project on their CV, so rehearse two of them down to the trade-offs you rejected.
The Cognite interview process
The rounds below are what candidates describe, not a published process, and reports say the order and number of stages can vary with the team. Each round lists what it covers and how to prepare.
Initial Screening
Candidates describe the process starting with an initial screening to judge fit for the role. Little else is reported about its format, so treat it as a conversation about your background and the role. One reported tip is to confirm the specific expectations for your role, remote work among them, with the recruiter at this stage. Use it to learn what the later rounds will look like for your team and level, since reports say the structure varies.
What to demonstrate
- Fit between your background and the role, as described in the screening
- How clearly you summarise your own experience and the stack you have used
- Whether your constraints and expectations line up with the role
How to prepare
- Write a two-minute summary of your career that lands on the listed requirements: a modern language, Git and testing
- Choose one or two CV projects and be ready to explain why you made each technical decision, not only what you built
- Prepare questions for the recruiter: remote expectations, which stages apply to your team and level, and what the coding environment will be
Remote Coding Session
Candidates report a remote coding session built to show hands-on coding skill. The coding questions candidates report cover four shapes: processing a stream of data with filtering criteria, a classic linked-list, tree or hash-map problem in an online environment, optimizing an existing algorithm for high-volume input, and refactoring code to be more readable using modern language features. Practise all four shapes in one language, talking through your reasoning as you type.
What to demonstrate
- Working code in your chosen language, built from sensible data structures
- Ability to state and improve time and space complexity
- Readable structure and naming, which reported tips stress over clever solutions
- How you clarify ambiguous requirements before coding
How to prepare
- Solve a stream-filtering exercise with a generator or iterator so that memory stays constant, then add a test for the empty input and for a record that fails the filter
- Take a nested-loop solution and rewrite it with a hash map or a sort plus two pointers, saying the before and after complexity
- Refactor a messy function into small pure steps using your language's functional or stream features, running tests after each change
- Practise in a bare editor with no autocomplete, narrating edge cases such as empty input, duplicates and a single element
System Design Discussion
The system design discussion is described as an in-depth conversation about architecture. Reported evaluation areas are scalability, latency versus throughput and data consistency, with example scenarios such as a notification service and an outage in a microservice. The reported design questions are about large-scale industrial data ingestion, frequent updates from thousands of IoT sensors, database architecture trade-offs and keeping an application maintainable as features are added.
What to demonstrate
- A logical approach to constraints rather than a list of buzzwords
- How the design behaves as users or data grow
- Awareness of latency, throughput and consistency trade-offs
- Handling of failure, such as an outage in one service
How to prepare
- Outline an ingestion design end to end: producers, a buffer between producers and storage, validation, deduplication, storage for recent and for historical data, and the read path
- Work out the load for a sensor fleet from assumptions you state, then say which component saturates first and how you would shed or batch load
- Prepare a comparison of a relational store, a key-value or wide-column store and a time-series store against access pattern, consistency and cost
- Prepare a short structure for a maintainable application: module boundaries, interfaces between them, test layers and how a new feature is added without touching the core
Behavioral Interview
Candidates report a dedicated behavioral interview aimed at understanding personality and soft skills, and say behavioral interviews are a standard part of the loop. The reported prompts here cover a technical disagreement, an architectural mistake, balancing code quality against delivery pressure and learning a new technology or domain on the fly. Prepare a small set of stories that can each be bent to answer several of those prompts.
What to demonstrate
- Your account of how you handled technical conflict
- Your judgement under delivery pressure
- How you describe learning and ownership in your own past work
How to prepare
- Write four story outlines, each with situation in two sentences, the decision you personally made, the evidence you used and the outcome
- Check each story for the word we, and replace it with what you specifically did
- For each story, note one thing you would do differently, so the reflection is ready
- Say each story aloud and trim it until the reasoning, not the background, takes most of the time
Peer and Leadership Conversations
The fifth stage is described as conversations with peers and leadership, framed in reports as an assessment of cultural fit. Candidates also report that role requirements stress collaboration with Data Scientists, Product Owners and fellow engineers, along with pair programming, code review and design discussions. Prepare to talk through shared technical decisions and how you work with people who have different priorities.
What to demonstrate
- How you work with peers and with people in other roles
- How you explain technical ideas to a mixed audience
- Whether your stories and CV projects hold together in more detail
How to prepare
- Rehearse the technical disagreement and architectural mistake stories until you can give both a short and a long version
- Prepare to explain one past design decision to a non-engineer in plain terms, covering what it costs and what it protects
- Ask your own questions about how the team reviews code and makes design decisions, and what technical debt it is carrying
Final Decision
The final stage is described as a decision based on all earlier assessments. No separate task or question set is reported for it, so there is little to rehearse beyond staying consistent with what you told earlier interviewers and being ready to discuss next steps.
What to demonstrate
- The combined picture from the earlier stages
- Any open points left from earlier conversations
How to prepare
- Keep a short write-up after each round of what you were asked and what you left unclear, and raise open points with the recruiter
- Make your start-date, location and remote-work constraints clear so they are not a surprise
- Review the stories you told in the behavioral rounds so any follow-up conversation stays consistent
Where candidates lose points
Starting the ingestion or IoT design by naming components before fixing the numbers
Open with the assumptions: how many devices, how often each reports, record size, how late or duplicate data is handled and who reads it. Then show the bottleneck those numbers produce, and let that choose the buffer, batching and storage.
Calling a database good or bad without tying it to an access pattern
Compare relational, key-value or wide-column and time-series stores on the same axes: query shape, write rate, consistency needed, retention and operational cost. End with a pick for a stated workload and the case in which you would switch.
Rewriting code in the refactoring question without protecting behavior
Write or confirm a few tests first, change one thing at a time, and rerun after each step. Explain why each stream or functional step reads better, and keep the result easy for a teammate to follow rather than clever.
Optimizing an algorithm by instinct instead of by its current cost
State the existing time and space complexity, find the repeated work, such as a nested scan or a recomputed value, then fix it with a hash map, a sort or a heap. Check the new complexity and a small edge case such as empty or duplicate input.
Telling behavioral stories in the first person plural, so your own decision never appears
For each story, write the sentence beginning with I that names the decision you made, the evidence behind it and the result. Keep the technical disagreement and architectural-mistake stories distinct, with a different decision in each.
Questions & practice
Implement a function to process a stream of data with specific filtering criteria.
Medium · Coding and Algorithms
Approach
- Clarify the contract first: is the stream bounded or unbounded, must output order match input order, and what happens to malformed records (skip and count, or raise)?
- Write it as a lazy pipeline: a Python generator (or Java Stream / JS iterator) that yields matching records one at a time, so memory stays O(1) per record and time is O(n * k) for k predicates.
- Model the criteria as a list of predicate functions combined with all(...) over a generator, which short-circuits; put the cheapest or most selective predicate first.
- Stateful criteria need extra structures: dedup within a time window uses a dict of key to last-seen time plus a deque for eviction; top-k uses a min-heap of size k (O(log k) per record).
- Cover edge cases out loud: empty stream, missing or None fields, NaN values (every comparison with NaN is false), out-of-order timestamps, and a test that feeds a plain list into the same function.
Follow-up
- How would you filter on a sliding window, such as readings whose 5-minute average exceeds a threshold?
- Events can arrive out of order by a few seconds. How do you still emit correct results?
- Throughput is too high for one process. How do you split the stream across workers without breaking per-key logic?
Solve a classic data structure problem (e.g., linked lists, trees, or hash maps) within an online coding envir
Medium · Coding and Algorithms
Solve a classic data structure problem (e.g., linked lists, trees, or hash maps) within an online coding environment.
Approach
- Name the pattern from the structure: linked lists use two pointers (fast/slow for cycle detection or finding the middle, a gap of k for k-th from the end) and a dummy head to simplify inserts and deletes; in-place reversal is O(n) time, O(1) space.
- Trees: choose DFS (recursion or explicit stack) or BFS (queue, for level order). Recursion uses O(h) stack, which is O(n) on a skewed tree, and Python's default recursion limit of 1000 makes an iterative stack safer for deep inputs.
- For BST validation, pass (low, high) bounds down the recursion; comparing a node only with its direct children misses violations deeper in the tree.
- Hash maps give average O(1) lookup (O(n) worst case under collisions): use them for complement lookups (two-sum), frequency counts and grouping. An LRU cache is a hash map plus a doubly linked list, or OrderedDict with move_to_end and popitem(last=False).
- Before saying done in the online editor, trace a small input by hand and run edge cases: empty list or null root, a single node, duplicate keys, and a cycle.
Follow-up
- Can you do it iteratively with O(1) extra space?
- How would you make the LRU cache safe for concurrent callers?
- What changes if keys are mutable objects or have a poor hash function?
Optimize an existing algorithm to handle high-volume inputs or reduce complexity.
Medium · Coding and Algorithms
Approach
- Read the existing code and state its complexity before changing anything; find the hot spot, typically a nested loop (O(n^2)), membership checks against a list (O(n) each) or building a string with += inside a loop, which can go quadratic.
- Apply the standard fixes and say what each costs: list to set or dict lookups (O(n*m) to O(n+m), at O(n) extra memory); sort plus two pointers or binary search (O(n log n)); prefix sums for repeated range sums (O(1) per query after an O(n) build); a size-k heap for top-k (O(n log k)); memoisation for repeated subproblems.
- For high-volume input, stop loading everything into memory: stream or chunk the input, batch I/O, and in Python move tight numeric loops into vectorised numpy operations.
- Keep the original as a reference: randomized tests compare old and new outputs, then benchmark at a realistic input size with timeit or a profiler such as cProfile to show the gain is real.
- Mention the trade-off you accepted: extra memory, more complex code, or an assumption about input (already sorted, bounded value range).
Follow-up
- The input no longer fits in memory. What do you change (external sort, chunked processing, map-reduce)?
- What if the input is usually nearly sorted?
- Can this be parallelised, and what part stays sequential?
Refactor existing code blocks to improve readability and leverage modern language features (e.g., using functi
Medium · Coding and Algorithms
Refactor existing code blocks to improve readability and leverage modern language features (e.g., using functional streams).
Approach
- Pin the current behaviour before touching it: write characterization tests on the existing code, then refactor in small steps and rerun them after each one.
- Apply the basic moves: extract functions with names that state intent, replace nested ifs with guard clauses, remove duplicated branches, and replace magic numbers with named constants.
- Turn accumulate-in-a-loop code into pipelines: Java list.stream().filter(...).map(...).toList() (Java 16+, returns an unmodifiable list) or Collectors.groupingBy for aggregation; Python comprehensions, generator expressions, itertools and collections.Counter; JS filter, map and reduce.
- Know the limits: avoid side effects inside map or forEach; a Java stream is single-use (reusing it throws IllegalStateException); checked exceptions do not fit cleanly in lambdas; parallelStream is not a free speed-up. Use Optional over null returns and records or dataclasses for data carriers.
- State that the complexity is unchanged and that the gain is readability and testability, then point to the test that proves behaviour stayed the same.
Follow-up
- When would you keep a plain loop instead of a stream pipeline?
- How would you split this refactor into reviewable pull requests?
- The code has no tests and depends on a database. How do you make it testable first?
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?
Stop tag and share joins from fanning out a page
Medium · join fan-out · semi-joins · many-to-many
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
Design a scalable system to handle large-scale industrial data ingestion.
Medium · System Design and Architecture
Approach
- Clarify the inputs and targets: source types (sensor time series, files, events from existing industrial systems), points per second, acceptable delay from source to query, late or out-of-order data, retention, and the main read queries.
- Outline the pipeline: authenticated ingestion API or connectors, then a durable partitioned log (Kafka or similar) keyed by source or series id so per-key order holds, then stream workers that validate, normalise units and timestamps, and write to storage.
- Split storage by access pattern: a time-series store for datapoints, a relational store for asset and series metadata, object storage for raw files. Make writes idempotent with upserts keyed on (series id, timestamp) so at-least-once delivery does not create duplicates.
- Reliability: a dead-letter queue for malformed records, replay from log offsets after a bad deploy, batching for write throughput, and backpressure when storage slows. Partition count caps consumer parallelism (one consumer per partition per group), and chatty sources create hot partitions.
- For reads, precompute rollups (min, max, average per interval) so long ranges do not scan raw points, and monitor consumer lag, data freshness and rejection rates.
Follow-up
- A source changes its schema without warning. What happens in your pipeline?
- How do you backfill years of history without slowing live ingestion?
- Where would you need exactly-once semantics, and what would it cost?
How would you architect a service to handle frequent updates from thousands of IoT sensors?
Medium · System Design and Architecture
Approach
- Estimate load out loud and state the assumption: for example 10,000 sensors at one reading per second is 10,000 writes per second; at about 100 bytes each that is roughly 1 MB/s, so steady volume and connection count matter more than raw bandwidth.
- Device side: MQTT publish/subscribe (QoS 0 at most once, 1 at least once, 2 exactly once) or batched HTTPS through site gateways that buffer during network outages; per-device credentials or certificates for authentication.
- Backend: broker to a log partitioned by sensor id, consumers that write to time-series storage partitioned by sensor and time bucket, and a key-value cache holding each sensor's latest value for dashboards. Alert rules run on the stream rather than by polling the database.
- Handle the messy cases: duplicates from at-least-once delivery (idempotent writes on sensor and timestamp), clock skew (store device and ingest timestamps), out-of-order arrival, and silent sensors (track last-seen time and alert on missed heartbeats).
- Read path: push live values to UIs over WebSocket or server-sent events, and serve historical charts from precomputed rollups.
Follow-up
- During an incident every sensor reports 100 times faster. What absorbs the burst?
- A firmware bug sends wrong units from one sensor model. How do you detect and correct it?
- How would you send configuration changes back to the devices?
Discuss the trade-offs between different database architectures for our specific product use cases.
Medium · System Design and Architecture
Approach
- Frame the comparison by workload, not brand: write rate, read shape (point lookup, time-range scan, large aggregation, relationship traversal), consistency needs, data volume and the team's operating cost.
- Relational databases (PostgreSQL) give ACID transactions, joins and constraints, which suit metadata, users and configuration; high-volume time series in them needs time-based partitioning and careful indexing, and horizontal sharding is mostly manual work.
- LSM-based wide-column stores (Cassandra-style) take very high write rates but every query must follow the partition key, and consistency is tunable per request. Columnar analytics stores compress well and aggregate fast over many rows, but handle single-row updates poorly.
- Hierarchies and relationships (asset trees, equipment connections) can use recursive CTEs in SQL or a graph database; say where traversal depth makes the graph store worth the extra system.
- Close with the cost of using several stores: keeping them consistent (transactional outbox or change data capture), more operational load, and which side of consistency vs availability you choose during a network partition.
Follow-up
- How do you keep a search index or cache consistent with the primary store?
- At what point would you shard PostgreSQL, and on what key?
- How would you answer a query like the average reading for all sensors under one asset over a month?
Explain how you would structure an application to ensure it remains maintainable as new features are added.
Medium · System Design and Architecture
Approach
- Organise code by feature or domain module with clear layers inside each: API handlers, services holding business logic, and repositories or adapters for storage and external calls. Dependencies point inward so business logic can be tested without a database.
- Make boundaries explicit: typed contracts between modules and for APIs (TypeScript types, OpenAPI schemas), versioned endpoints, no shared mutable global state, and dependency injection so implementations can be swapped in tests.
- Protect change with tests: many unit tests on domain logic, contract or integration tests at module and API boundaries, a few end-to-end tests, all run in CI; use feature flags to merge unfinished work safely.
- On a React front end: feature folders, custom hooks for data fetching and logic, presentational components kept free of side effects, and server data held in a fetching cache rather than a global store.
- Name the trade-off: abstracting too early adds indirection, so extract shared code once duplication is real, enforce module boundaries with lint rules, and record major decisions in short architecture decision records.
Follow-up
- When would you split this into separate services rather than keep a modular monolith?
- Another team depends on an API you need to change in a breaking way. How do you roll it out?
- How do you pay down technical debt in a module every feature touches?
Tell us about a project where you noticed an architectural mistake. What did you do to address it?
Medium · Behavioral and Leadership
Approach
- Choose a concrete architectural mistake you saw yourself, such as a synchronous call chain that cascaded failures, two services sharing one database, a partition key that created a hot shard, or writes without idempotency. Open with the symptom and the evidence (incident, metrics, profiling), not hindsight opinion.
- Explain the root cause in architectural terms and why the original choice made sense under its constraints at the time, without blaming the people who made it.
- Describe what you did: wrote down options with their costs (local patch vs redesign), won agreement, and migrated in steps (strangler pattern, dual writes with backfill, a feature flag) with a rollback plan.
- Give the measured result (latency, incident count, deploy frequency) and the process change you added, such as a design review checklist, load tests or a decision record.
- Avoid the two answers that sink this question: a story where you only pointed out the flaw and someone else fixed it, and a big-bang rewrite with no measurement. Keep the actions in first person singular.
Follow-up
- How did you convince the engineers who built the original design?
- If the migration had to stop halfway, what state would the system be in?
- What would have caught this mistake at design time?
Listing latency scales with page size, not with filters
Easy · n+1 · latency · query batching
The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.
Approach
- Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
- Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
- Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
- Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.
Follow-up
- The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
- How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?
Suggested practice checklist
Seven days built around the reported rounds: one day to map the loop, four days of coding and design practice on the reported questions, a behavioral day and a mixed run-through.
Day 1 — Map the loop and choose your language
- List the six reported rounds, and under each note which of the twelve reported questions belongs to its question type: coding, design or behavioral
- Choose one language from the listed examples (Python, Java, Go or JavaScript/TypeScript) and set up a bare editor with a test runner
- Write your two-minute career summary and the two CV projects you can defend decision by decision
Deliverable: A one-page loop map, a working test setup in your chosen language, and a written career summary with two project outlines.
Day 2 — Streams, filtering and refactoring
- Implement the stream-filtering function with an iterator or generator, so it never holds the whole input, and write tests for empty input and for records that fail the filter
- Refactor a loop-heavy function using the functional or stream features of your language, keeping the tests green after each step
- Say the time and space complexity of both versions aloud
Deliverable: A working stream filter with tests, and a before-and-after refactor with a written complexity note for each.
Day 3 — Data structures and algorithm optimization
- Solve a linked-list, a tree and a hash-map problem in a plain online editor, narrating edge cases
- Take a brute-force solution, state its complexity, then improve it with a hash map, sorting or a heap
- Write a one-line explanation of why each data structure fits its access pattern
Deliverable: Three solved classic problems and one optimized algorithm, each with a stated before and after complexity.
Day 4 — Ingestion and sensor-update design
- Outline an ingestion design with a buffer, validation, deduplication, hot and historical storage and a read path, plus the failure story when storage is down
- Estimate the load for a sensor fleet from assumptions you state, then name the first bottleneck and how you would batch or shed load
Deliverable: Two written design outlines (ingestion and sensor updates), each with stated assumptions, a bottleneck and a failure mode.
Day 5 — Database trade-offs and maintainable structure
- Write a comparison table of relational, key-value or wide-column and time-series storage across query shape, write rate, consistency, retention and cost
- Outline how you would structure an application so a new feature does not touch the core: modules, interfaces and test layers
Deliverable: A storage comparison table, a one-page application structure outline and a written debugging sequence from the practice item.
Day 6 — Behavioral stories
- Write outlines for the technical disagreement, architectural mistake, quality-versus-speed and learn-on-the-fly stories, each with the decision you made, the evidence and the outcome
- Add a second, shorter version of each story and one thing you would change
- Say each aloud and check that your own decision appears in the first minute
Deliverable: Four story outlines with short and long versions, each naming your own decision and one thing you would change.
Day 7 — Mixed run-through and CV review
- Do a full mixed session: one optimization problem in the editor, one design question by voice, one behavioral story
- Review both CV projects for the trade-offs you rejected, and prepare three questions of your own for peers and leadership
- Write the list of anything still unclear to raise with the recruiter
Deliverable: One recorded or written mixed session with notes on weak spots, plus a list of three questions for the team and your open points for the recruiter.
Behavioural preparation
Candidates report that behavioral interviews are a standard part of this loop, and two of the reported prompts are about technical judgement. For a Software Engineer role, prepare stories where you made a decision, backed it with evidence and can name what you would change. The reported questions come first, followed by two prompts built from the CV-review advice.
- Tell us about a project where you noticed an architectural mistake. What did you do to address it?
- Walk through a project on your CV and explain why you made each major technical decision.
- Describe a time you worked with a Product Owner or Data Scientist whose priorities differed from yours.
Describe a time you had to deal with a significant technical disagreement within your team. How did you resolv
Medium · Behavioral and Leadership
Describe a time you had to deal with a significant technical disagreement within your team. How did you resolve it?
Approach
- Choose a disagreement that was genuinely technical, with two defensible options such as a build-versus-adopt call, a schema design, a service split or a testing strategy, and where you held a real stake. State each position fairly in one sentence, including the one you argued against.
- Show the move from opinion to evidence: a small spike, benchmark, load test or written comparison against agreed criteria such as latency, operational cost, reversibility and time to deliver. Name the criteria the group agreed on before looking at results.
- State how the decision was made: the owner decided, the group agreed, or a time-boxed experiment was set with a date to revisit. Say what you did when the outcome went against you, ideally that you committed to it and helped deliver it.
- Close with the measurable outcome and one thing you would change. The mistake that sinks this story is casting colleagues as obstructive while you were simply right, or choosing a mild difference of taste that never put anything at risk.
Follow-up
- What did you concede during the disagreement, and was that the right call?
- What if the other person had been more senior or the evidence had been inconclusive?
- How did the working relationship change afterwards?
How do you balance the need for high-quality code with the pressure to deliver features quickly?
Medium · Behavioral and Leadership
Approach
- Answer with a rule and a story, not a philosophy. The rule: decide up front which things are non-negotiable on the change at hand, such as correct data handling, tests on the path you changed and a way to roll back, and which can wait, such as polish, secondary options and internal naming.
- The story: a real deadline where you cut scope rather than quality, for example shipping a narrower slice behind a feature flag with tests on the core path, instead of a wide slice with gaps.
- Explain how you made the trade-off visible to the Product Owner in terms they could decide on, such as what ships on the date, what moves and what risk remains, and how the deferred work was recorded as a ticket with an owner rather than left as a vague promise.
- Give evidence: defects after release, a rollback avoided or the time saved. Include a case where you chose to move the date because the risk was too high. The mistake to avoid is claiming you never compromise, or that speed always wins.
Follow-up
- Tell me about a shortcut that came back to cost you. What did you change afterwards?
- How do you decide which technical debt is worth paying down now?
- How do you handle a Product Owner who wants the date kept and the scope unchanged?
Give an example of a time you had to learn a new technology or domain on the fly to solve a problem.
Medium · Behavioral and Leadership
Approach
- Pick a case with real pressure and a subject that was new to you, such as a language, a storage system, a protocol or an unfamiliar business domain. Say in one sentence what had to be solved and by when, then move on to how you learned.
- Describe a method, not a course list: split what you needed to know from what was only interesting, read primary documentation or source, build the smallest working experiment, and ask a specific question of someone who knew the area rather than a vague request for help.
- Explain how you limited risk while still learning: pairing, small pull requests, extra tests around code you did not fully understand, and review by someone experienced. Be honest about what you got wrong first, because it makes the story credible.
- Finish with the outcome and what stayed behind: a short write-up for the team, a reusable helper or a quicker route next time. The mistake is claiming mastery within days or describing only passive reading with no shipped result.
Follow-up
- How did you check that your understanding was correct before it reached production?
- What would you have done if nobody around you knew the area?
- What did you get wrong at first, and how did you find out?