cswork / libraryOpen practice workspace

Interview guides · Canterr

Canterr Software Engineer Interview Guide

Canterr Software Engineer interview guide: the three reported rounds, reported coding, system design and behavioral questions, and a 7-day prep plan.

The 60-second brief

Candidates describe Canterr as a company that builds software applications for users across several platforms, and the role information names e-commerce and technology as the sectors its work touches. For a Software Engineer, the reported work spans backend systems and user-facing applications: improving existing applications, building new features, integrating third-party services, taking part in code reviews, and tracking down and fixing defects. The role is described as working alongside product managers, designers and other engineers. Nothing reported names a specific product, team or internal system, so prepare for general engineering fundamentals rather than for one codebase.

This guide is for candidates interviewing for a Software Engineer position at Canterr. It covers the three reported stages (Recruiter Call, Technical Interviews, Practical Assessments), the languages and frameworks the role lists (Java, Python or JavaScript; Spring Boot or React), and the reported questions on fundamentals, system design, problem-solving, coding and behavior. Each behavioral question has its own approach and likely follow-ups, and a seven-day plan sequences the whole preparation.

Candidates report three stages over roughly 3-5 weeks: a Recruiter Call, a series of Technical Interviews covering coding, system design and problem-solving, and Practical Assessments built around hands-on tasks.

Focus areas

  • Coding Interviews
  • System Design
  • Java

Overview

The reported question set is broad rather than deep in one area. It mixes language and architecture fundamentals (object-oriented versus functional programming, REST versus SOAP, design patterns, microservices), system design (a URL shortener, a chat application, high availability, data consistency, authentication), problem-solving scenarios (a slow system, a bug found before a release, technical debt, refactor versus rewrite, project metrics), classic coding questions (reversing a linked list, binary search, sorting, palindromes, dynamic programming) and behavioral prompts. Candidates describe the list as a sample of patterns, not a fixed script, so aim to understand each topic well enough to handle a variation.

The role lists Java, Python or JavaScript as the languages, Spring Boot or React as the frameworks, plus familiarity with cloud services and deployment. DevOps practice, microservices, Agile tooling and SQL or NoSQL databases are listed as extras. Pick one language for the coding questions and be able to write it fluently without an editor's help. Then keep a second, smaller area ready for the stack you will be asked about: for Java that means Spring Boot request handling, and for JavaScript it means React components and state.

Because the day-to-day work includes code reviews, defect fixing, third-party integrations and working with product managers and designers, the problem-solving and behavioral questions carry real weight alongside the algorithms. Prepare a debugging sequence you can say out loud, a decision framework for technical debt and refactoring, and a set of stories in which you personally made the call.

The final stage is described only as hands-on tasks, so the safest preparation is to be able to build and run something small in your chosen language: a working endpoint or component, a few tests, a short README, and a clear explanation of how you would deploy it. The plan below spends one day on coding fundamentals, one on sorting and dynamic programming, one on fundamentals, one on system design, one on problem-solving and SQL, and the last on a hands-on rehearsal plus behavioral stories.

The Canterr interview process

Candidates describe three stages, in this order: Recruiter Call, Technical Interviews, Practical Assessments. Teams and roles can vary, so confirm the details of each stage with your recruiter.

Recruiter Call

Candidates describe this stage as an initial call about your background and your interest in the role. It is the point to show how your experience maps onto the languages and frameworks the role lists, and to learn what the later stages involve. Go in with a clear account of two or three projects (the problem, your part, the stack, the outcome) and a short list of questions about the process.

What to demonstrate

  • How your background lines up with the role's languages and frameworks
  • Your stated interest in the Software Engineer role

How to prepare

  • Write a short account of your last two projects: the problem, what you personally built, the stack, and the result.
  • Prepare questions for the recruiter: what the Technical Interviews cover, whether the Practical Assessments are live or done on your own, and whether the position is permanent or short-term.
  • Know your availability, location constraints and notice period so you can answer logistics questions directly.

Technical Interviews

Candidates report a series of interviews focused on coding, system design and problem-solving. This is where the coding questions, the design questions and the case-style scenarios are practised: reversing a linked list, binary search, sorting, palindromes and dynamic programming on the coding side, a URL shortener or chat application on the design side, and debugging or release-risk scenarios on the problem-solving side. Plan to talk through your reasoning aloud, because a series of interviews rewards consistency across several conversations.

What to demonstrate

  • Working code for data-structure and algorithm problems in your chosen language
  • System design reasoning and trade-offs for the reported design questions
  • Problem-solving: how you break down a slow system, a late bug or a refactoring decision

How to prepare

  • Implement the five reported coding topics from memory in one language, including empty and single-element inputs, and state the time and space cost of each.
  • Rehearse a complete design for a URL shortener and for a chat application: requirements, API, storage, scaling and failure handling.
  • Write a debugging sequence for a slow system (measure, narrow, fix, verify) and be ready to apply it to a concrete example from your own work.
  • Prepare a contrast of microservices and a monolith that names costs as well as benefits.

Practical Assessments

Candidates describe this stage as hands-on tasks that test your technical skills. Reports do not say whether the tasks are live or done on your own, or which tools they use, so ask the recruiter. The preparation that holds up either way is being able to produce small, working, tested code in the language and framework you know best, and explain your decisions as you go.

What to demonstrate

  • Ability to produce working code on a practical task
  • Hands-on skill in the language and framework you choose from the role's list

How to prepare

  • Build a small service or component in your main stack (Spring Boot, Python or React) with a few tests, and note how you would deploy it.
  • Practise reading an unfamiliar task description, listing assumptions, and delivering the smallest working version before polishing.
  • Keep a short README habit: how to run it, what you assumed, and what you would do next.

Where candidates lose points

Reversing a linked list, but losing the rest of the list or failing on empty and single-node input

Write the iterative version with previous, current and next pointers, saving the next pointer before you overwrite it. Test an empty list, one node and two nodes aloud, then be ready to describe the recursive version and its stack cost.

Binary search with an off-by-one error, an infinite loop or the wrong boundary

State the invariant (what the low and high bounds mean) before writing the loop, compute the midpoint as low plus half the gap, and test duplicates, a missing target and a single element. Know the variant that returns the first occurrence.

Drawing boxes for the URL shortener or chat application without requirements, numbers or failure handling

Start by fixing scope and rough volume, separate the read and write paths, pick an ID or key scheme and a storage model, add caching, then say what happens when a component or region fails and which data may be stale.

Listing the benefits of microservices, REST or functional programming without any costs or conditions

For each concept, say when it is the wrong choice. For microservices that means network failure, distributed transactions and operational overhead compared with a monolith. For REST versus SOAP, say what you give up with each contract style.

Answering the design-pattern project question with a textbook definition, or telling a behavioral story in the first person plural

Name the real problem first, then the pattern and what it replaced. In every story, say what you personally did and decided, and keep the team's work as context only.

Questions & practice

Write a function to reverse a linked list.

Medium · Coding / Algorithms

Approach

  1. Iterative three-pointer reversal: prev = null and curr = head. In a loop: save next = curr.next, set curr.next = prev, then prev = curr and curr = next. Return prev as the new head. O(n) time, O(1) extra space.
  2. Edge cases: an empty list, a single node and two nodes. Check that the old head ends up pointing to null so no cycle is created.
  3. Recursive version: reverse the rest of the list, set head.next.next = head, then head.next = null, and return the new head from the recursion. It uses O(n) stack, which can overflow on long lists (Python's default recursion limit is 1000), so I would ship the iterative one.
  4. Test it by building a list from an array, reversing it and converting it back. Reversing twice should return the original order.
  5. Know the variants: reverse the sublist between positions m and n in one pass, reverse in groups of k, and reverse a doubly linked list by swapping prev and next on each node.

Follow-up

  • Reverse only the nodes between positions m and n in a single pass.
  • Reverse the list in groups of k, leaving a shorter tail as it is.
  • Check whether a linked list is a palindrome using O(1) extra space.

How would you implement a binary search algorithm?

Medium · Coding / Algorithms

Approach

  1. State the precondition: a sorted array, or more generally a monotonic predicate. Closed-interval loop: lo = 0, hi = n - 1, while lo <= hi compute mid = lo + (hi - lo) / 2 (which avoids int overflow in Java), return mid on a match, otherwise set lo = mid + 1 or hi = mid - 1. Return -1 when not found. O(log n) time, O(1) space.
  2. Lower-bound variant on a half-open range: lo = 0, hi = n, while lo < hi, if a[mid] < target then lo = mid + 1, else hi = mid. It returns the first index whose value is >= target, which handles duplicates and also gives the insertion point.
  3. Edge cases: an empty array, one element, a target below the minimum or above the maximum, duplicates, and the infinite loop you get when lo = mid is used without the +1.
  4. Library behaviour: Java's Arrays.binarySearch returns -(insertion point) - 1 when the key is missing and does not say which duplicate it finds. Python's bisect_left and bisect_right return the leftmost and rightmost insertion points.
  5. Binary search on the answer: when feasibility is monotonic in some value (minimum ship capacity, integer square root), search over that value instead of an index.

Follow-up

  • Find the first and last position of a target in a sorted array with duplicates.
  • Search a rotated sorted array in O(log n).
  • Find the minimum ship capacity that delivers all packages within D days.

Solve a problem involving sorting an array with a specific time complexity.

Medium · Coding / Algorithms

Approach

  1. Clarify the constraint first. Comparison sorts cannot beat O(n log n) in the worst case. Bounded integer keys allow counting sort in O(n + k) or radix sort in O(d(n + b)). Three distinct values sort in one O(n) pass with O(1) space using the Dutch national flag partition.
  2. Know the trade-offs. Merge sort is O(n log n) in the worst case, stable, and needs O(n) extra space. Quicksort is O(n log n) on average and O(n^2) in the worst case, in place and not stable, and a random pivot makes the worst case unlikely. Heapsort is O(n log n) in the worst case with O(1) extra space and is not stable. Insertion sort is O(n^2) but O(n) on nearly sorted input and fast for small n.
  3. Know the libraries. Java's Arrays.sort uses dual-pivot quicksort for primitives and a stable TimSort for objects. Python's sort is a stable Timsort-family merge sort, O(n log n) in the worst case and O(n) on input that is already sorted.
  4. When you do not need a full sort: top-k with a size-k heap in O(n log k), and the kth element with quickselect in O(n) on average.
  5. Edge cases: duplicates, negative values in counting sort (shift by the minimum), stability when sorting by a secondary key, and empty or single-element input.

Follow-up

  • Sort an array of 0s, 1s and 2s in one pass with O(1) extra space.
  • How would you sort a file that is too large to fit in memory?
  • Why does sorting by two keys in two passes need a stable sort?

Can you write a function to check if a string is a palindrome?

Medium · Coding / Algorithms

Approach

  1. Two pointers: i at the start and j at the end, moving inward while the characters match. O(n) time and O(1) extra space. Contrast it with s == s[::-1], which is also O(n) time but builds an O(n) copy.
  2. Clarify the rules before coding: is the check case-sensitive, should spaces and punctuation be skipped (Character.isLetterOrDigit in Java, str.isalnum in Python), and how should Unicode be handled? Java chars are UTF-16 code units, so surrogate pairs need comparison by code point.
  3. Loop: while i < j, skip characters that are not alphanumeric on each side, compare the lowercased characters, return false on a mismatch, otherwise move both pointers. Return true when they meet.
  4. Tests: the empty string, a single character, even and odd lengths, a string made only of punctuation, "A man, a plan, a canal: Panama" (true) and "race a car" (false).
  5. Variants: allow at most one deletion (on the first mismatch, check whether skipping the left or the right character leaves a palindrome, still O(n)), and longest palindromic substring by expanding around each centre in O(n^2), or Manacher's algorithm in O(n).

Follow-up

  • Can the string become a palindrome by deleting at most one character?
  • Find the longest palindromic substring.
  • Check whether an integer or a linked list is a palindrome without converting it to a string.

Explain your approach to solving a dynamic programming problem.

Medium · Coding / Algorithms

Approach

  1. Recognise when DP applies: the problem has optimal substructure and overlapping subproblems, and usually asks for an optimum, a count or a yes/no answer over prefixes, grids, intervals or subsets.
  2. Follow a fixed sequence: define the state in one sentence (dp[i] = ...), write the recurrence and base cases, choose top-down memoisation or bottom-up tabulation, decide the iteration order so every dependency is computed first, and say where the answer sits.
  3. Work an example aloud. Minimum coin change: dp[0] = 0 and dp[a] = min over coins c of dp[a - c] + 1, with unreachable amounts held at infinity. That is O(amount x coins) time and O(amount) space. Show why greedy fails: with coins {1, 3, 4} and amount 6, greedy takes 4 + 1 + 1 (three coins) while 3 + 3 needs two.
  4. Optimise and reconstruct: when each row depends only on the previous one, keep a single row. For example, LCS is O(mn) time and its space drops to O(min(m, n)). In 0/1 knapsack with a 1D array, loop capacity downwards so each item is used at most once. Store parent choices to recover the actual solution.
  5. Pitfalls: recursion depth in top-down Python (prefer bottom-up for large inputs), off-by-one errors in the base row, and confusing combinations with permutations when counting.

Follow-up

  • Count the ways to make change instead of the minimum coins. Which loop goes on the outside, and why?
  • Reduce your 2D table to a single row.
  • How would you output the items in the optimal solution, not just its value?

Explain why the owner filter ignores the listing index

Medium · composite index · query plans · sort elimination

The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.

Approach

  1. Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
  2. For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
  3. Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
  4. Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.

Follow-up

  • 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
  • A dashboard runs this for 40 owners in one page load. What changes about the design?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?

What are the differences between object-oriented and functional programming?

Medium · Technical / Domain Questions

Approach

  1. Start with the core split: object-oriented code bundles mutable state with the methods that change it (encapsulation, inheritance, subtype polymorphism), while functional code builds programs from pure functions over immutable values, combined through composition and higher-order functions.
  2. Name the practical consequences. A pure function is referentially transparent, so it tests without mocks and is safe to run in parallel. Shared mutable objects need locks, confinement or defensive copies.
  3. Explain the expression problem. In OOP, adding a new type is one new subclass, but adding a new operation touches every class. With algebraic data types and pattern matching it is the other way round.
  4. Show that real code mixes the two. In Java that means lambdas, Stream map/filter/reduce and records. In Python it means comprehensions, functools and frozen dataclasses. One example: I turned a stateful discount calculator into a pure function that takes a cart and returns a new total.
  5. Give the costs. Immutability means extra allocation and copying, which persistent data structures reduce through structural sharing. Deep inheritance trees produce fragile base classes, which is why I prefer composition to inheritance.

Follow-up

  • When would you pick a class hierarchy over a sum type with pattern matching?
  • How do you keep side effects such as I/O and database writes out of a functional core?
  • Is Java's Stream API functional programming? What does it still not give you?

Can you explain the concept of RESTful APIs and how they differ from SOAP?

Medium · Technical / Domain Questions

Approach

  1. Define REST as an architectural style, not a protocol: resources named by URIs, a uniform interface through HTTP methods, stateless requests, cacheable responses and representations, usually JSON. Hypermedia links (HATEOAS) are the strict form, and few APIs implement them.
  2. Define SOAP as a protocol: an XML envelope with Header, Body and Fault elements, usually sent with HTTP POST, a contract described in WSDL, and WS-* extensions such as WS-Security and WS-ReliableMessaging.
  3. Get the method semantics right. GET is safe and idempotent, PUT and DELETE are idempotent, POST is neither, and PATCH is not guaranteed to be idempotent. Pair them with status codes: 201 with a Location header, 404, 409, 422, 429.
  4. Compare the trade-offs. REST responses can be cached by HTTP caches and CDNs and are light enough for browser and mobile clients. Because SOAP sends everything through POST, HTTP caching does not apply, but it gives a strict typed contract and message-level security across intermediaries, so it still shows up in enterprise and banking integrations.
  5. Cover errors and versioning. SOAP returns Fault elements. REST uses status codes plus a body such as application/problem+json (RFC 9457), and is versioned through the URL path or a header. Mention gRPC and GraphQL as other options when the question widens.

Follow-up

  • How would you make a POST that creates an order safe for a client to retry?
  • How do you version a REST API without breaking clients that are already live?
  • When would you choose gRPC over REST between internal services?

Describe a project where you implemented a design pattern. What pattern did you use and why?

Medium · Technical / Domain Questions

Approach

  1. Pick one pattern and the problem that forced it. A good example is Strategy replacing a growing if/else over shipping or payment providers: say what the code looked like before and which change the pattern made cheap.
  2. Walk through the implementation: one interface, one class per variant, and a factory or registry that picks the class at runtime. In Spring Boot that can be injecting a Map<String, ProviderStrategy> of beans keyed by name.
  3. Say why you chose it over the alternatives. A plain map of functions can be enough for Strategy. Decorator beats subclassing for adding retries or logging around a client. Adapter wraps a third-party API behind your own interface. Observer or events decouple side effects from the main flow.
  4. State the cost honestly: more classes, more indirection, and code that is harder to trace. Say when you would not use the pattern, for example two cases that never change.
  5. Close with evidence: each strategy unit-tested in isolation, and the next provider added by writing one new class without editing the dispatcher.

Follow-up

  • Where does the logic that selects a strategy live, and how is that logic tested?
  • Which pattern have you seen overused, and what did it cost the codebase?
  • How would the design change if each provider needed different configuration and credentials?

How do you ensure code quality and maintainability in your projects?

Medium · Technical / Domain Questions

Approach

  1. Start with automated gates in CI that block a merge: a formatter and linter (Checkstyle or SpotBugs for Java, ruff and mypy for Python, ESLint for JavaScript), plus the unit test suite on every pull request.
  2. Describe the test strategy as a pyramid: many fast unit tests, fewer integration tests against real dependencies (for example Testcontainers for the database), and a handful of end-to-end tests. Every bug fix starts with a test that reproduces the bug.
  3. Explain code review: small pull requests, reviewers who focus on design, naming, error handling and missing edge cases because tools already enforce style, and written reasons when a change is blocked.
  4. Cover design for maintainability: clear module boundaries, dependency injection so units can be tested, small functions with one responsibility, and refactoring in small steps under test coverage rather than in big rewrites.
  5. Say how you would know it works: escaped defects, change failure rate, review turnaround and churn-times-complexity hotspots. Treat coverage as a floor that flags untested code, not as a target.

Follow-up

  • What coverage threshold would you set, and how can a high number still mislead?
  • How do you raise quality in an existing codebase that has almost no tests?
  • What do you do when you and a reviewer disagree on a design point in a pull request?

What are the advantages of using microservices architecture?

Medium · Technical / Domain Questions

Approach

  1. List the real advantages. Each service deploys independently, so one team can release without a whole-application release train. Hot services scale on their own. A failure stays contained when timeouts and bulkheads are in place. Each service can pick its own technology. Codebases are smaller and each has one owning team.
  2. Pair every advantage with its cost. In-process calls become network calls, which adds latency and partial failure. Cross-service transactions become sagas. Each service owns its database, so data is duplicated. Service discovery, tracing and a pipeline per service add operational work. Local development and end-to-end testing get harder.
  3. Explain where to draw boundaries: along business capabilities or bounded contexts, such as catalogue, cart, orders and payments in an online store, with each service owning its data. A shared database couples the services back together.
  4. Name the infrastructure the style needs: an API gateway, service discovery, timeouts, retries with backoff and circuit breakers, distributed tracing such as OpenTelemetry, central logs, and consumer-driven contract tests.
  5. Give the counter-case. For a small team or an early product I would start with a modular monolith, and extract a service only when one module's scaling needs or release cadence clearly differ from the rest.

Follow-up

  • How would you keep orders and inventory consistent when they live in separate services?
  • How do you stop one slow downstream service from cascading into an outage?
  • What signals would tell you two services should be merged back together?

Design a URL shortening service. What considerations would you take into account?

Medium · System Design / Architecture

Approach

  1. Fix the scope: create a short link, redirect, optional custom alias and expiry, click analytics. State that the load is read-heavy, with far more redirects than creates, then size storage from an assumed creation rate that you say out loud.
  2. Key generation: base62-encode a unique numeric id. Seven characters give 62^7, about 3.5 trillion codes. Hand each app server a block of ids from a counter service so there is no single hot counter. The alternative, hashing the long URL and taking a prefix, needs collision checks. Sequential codes can be enumerated, so shuffle or encrypt the id if that matters.
  3. Storage: a key-value mapping from short_code to long_url, owner, created_at and expires_at, read by primary key and sharded by short code. A NoSQL store or a sharded SQL table both fit, and a unique constraint on custom aliases settles races.
  4. Read path: cache-aside in Redis, plus a CDN, in front of the database. Choose the redirect code on purpose. A 301 is cached by browsers, which cuts load but hides repeat clicks from analytics. A 302 sends every click through the service.
  5. Analytics and abuse: publish click events to a queue and aggregate them asynchronously, outside the redirect path. Rate-limit creation, check destinations against malware lists, return 404 or 410 for expired links and clean them up with a background job.

Follow-up

  • How do you guarantee two servers never hand out the same short code?
  • What changes if the same long URL must always return the same short code?
  • How would you expire and delete billions of old links without hurting redirect latency?

How would you structure a scalable chat application?

Medium · System Design / Architecture

Approach

  1. Fix the scope: one-to-one and group chat, message history, delivery and read receipts, online presence, and push notifications for users who are offline.
  2. Connections: clients keep persistent WebSocket connections to a fleet of gateway servers behind a load balancer that supports long-lived connections. A session registry, for example a Redis map from user to gateway, tells the message service where each online user is connected.
  3. Message flow: the chat service gives each message a sequence number that increases within its conversation, persists the message, then fans it out to online members over pub/sub. Offline members get a push notification and, when they reconnect, fetch everything after the last sequence number they saw.
  4. Storage: partition messages by conversation_id and cluster them by sequence number. A wide-column store such as Cassandra suits append-heavy writes and range reads. A client-generated message id makes retries idempotent, which gives at-least-once delivery without duplicates.
  5. Scaling trade-offs: fan out on write for small groups and on read for very large channels. Track presence with heartbeats that expire through a TTL, and batch or throttle presence updates so they do not swamp the message traffic.

Follow-up

  • How do you keep message order within a conversation when two members send at the same moment?
  • What happens to undelivered messages when a gateway server crashes?
  • If you add end-to-end encryption, which server-side features stop working?

Explain how you would approach designing a high-availability system.

Medium · System Design / Architecture

Approach

  1. Set the targets first: an availability SLO, recovery time (RTO) and recovery point (RPO) objectives. 99.9% allows about 8.76 hours of downtime a year and 99.99% about 52.6 minutes, and each extra nine costs more. Dependencies in series multiply: two 99.9% services in a chain give about 99.8%.
  2. Remove single points of failure. Run several stateless instances across availability zones behind a load balancer with health checks. Replicate the database with automatic failover to a standby. For stricter targets, add a second region, active-passive or active-active.
  3. Handle failure in the code: a timeout on every remote call, retries with exponential backoff and jitter for idempotent operations only, circuit breakers, bulkheads, and graceful degradation such as serving cached data or switching off non-essential features.
  4. Make change safe, because deployments often trigger outages: rolling, blue-green or canary releases, feature flags, and a rollback that has been tested.
  5. Verify it: monitor latency, traffic, errors and saturation, alert on SLO burn rate, run failover drills, and restore backups on a schedule so you know they work.

Follow-up

  • When the primary database fails over, what happens to writes that were in flight, and can any be lost?
  • How do you stop client retries from turning a partial outage into a full one?
  • In active-active across regions, how do you handle conflicting writes to the same record?

What are some strategies for managing data consistency in distributed systems?

Medium · System Design / Architecture

Approach

  1. Frame the choice with CAP and PACELC: during a network partition a system gives up either consistency or availability, and without a partition it trades latency against consistency. Choose per data type, for example strong consistency for balances and stock counts, eventual consistency for feeds and view counts.
  2. Strong-consistency tools: a single leader with synchronous replication, consensus (Raft or Paxos) for metadata and leader election, and quorum reads and writes where R + W > N so every read overlaps the latest write.
  3. Across services, avoid two-phase commit because a coordinator failure blocks everyone. Use sagas with compensating actions, and the transactional outbox (or change data capture) so the database write and the published event cannot diverge. Consumers stay idempotent by keying on the event id.
  4. Concurrency control: optimistic locking with a version column (UPDATE ... WHERE id = ? AND version = ?), and conditional or compare-and-set writes. For multi-leader conflicts: last-writer-wins, which silently drops data, versus vector clocks or CRDTs.
  5. Client-facing guarantees: read-your-writes, by reading from the leader after a write or passing a version token, and monotonic reads through session stickiness. Be explicit about how stale a cache is allowed to be.

Follow-up

  • Walk through a saga for an order where payment fails after the inventory has been reserved.
  • Why is writing to the database and then publishing to a queue not safe on its own?
  • With N = 3 replicas and a read-heavy workload, which R and W would you choose, and what do you give up?

Describe how you would handle user authentication in a web application.

Medium · System Design / Architecture

Approach

  1. Credentials: never store passwords in plain form. Hash them with a slow, salted, adaptive algorithm (Argon2id, bcrypt or scrypt). Rate-limit login attempts per account and per IP, and offer MFA through TOTP or WebAuthn.
  2. Sessions: compare server-side sessions (a random id in a cookie, state in Redis, easy to revoke) with JWT access tokens (verified without a lookup, hard to revoke). If I use JWTs, I keep access tokens short-lived and add rotating refresh tokens with reuse detection.
  3. Browser hardening: HttpOnly, Secure and SameSite cookies, a CSRF token for cookie-authenticated requests that change state, HTTPS everywhere with HSTS. A token in localStorage can be read by any XSS payload.
  4. Account flows: reset tokens that are single-use, short-lived and stored hashed; email verification; the same response for unknown and known emails so the endpoint does not reveal which accounts exist; OAuth 2.0 authorization code with PKCE and OpenID Connect for social login or SSO.
  5. Keep authentication separate from authorization: check roles and permissions on the server for every request (in Spring Boot, the Spring Security filter chain and method security), and log sign-ins and other security events for auditing.

Follow-up

  • With JWTs, how do you sign a user out of every device right away?
  • In a React single-page app, where would you keep tokens, and why there?
  • How would you move existing SHA-256 password hashes to Argon2id without forcing every user to reset?

If a critical bug was found just before a release, what steps would you take?

Medium · Problem-Solving / Case Studies

Approach

  1. Triage first: reproduce the bug and measure the blast radius (data loss, security, payments, or only cosmetic). Then find out whether this release introduced it or it is already live in production, because a pre-existing bug may not justify holding the release.
  2. Lay out the options and decide with the release owner and product: hold the release, ship with a fix, ship with the affected feature switched off behind a flag, or ship and follow with a patch. Base the call on severity, how many users it reaches and how risky the fix is.
  3. Fix path: the smallest safe change, a failing test written before the fix, a code review, and the full regression suite on the release branch. Cherry-pick the fix to both the release branch and main so it does not return in the next release.
  4. Communicate the impact, the decision and the new timeline to stakeholders, and have a rollback plan ready if the release has already gone out partly.
  5. Afterwards, run a blameless review of why tests and staging missed it, and add the missing test, alert or check to the pipeline.

Follow-up

  • The fix is risky and the date is tied to a customer commitment. What do you do?
  • How do you make sure a hotfix on the release branch is not lost from main?
  • What would you change in the pipeline so this class of bug is caught earlier?

Describe how you would balance technical debt with new feature development.

Medium · Problem-Solving / Case Studies

Approach

  1. Make the debt visible: list each item with its cost in incidents, on-call pages and slower delivery, and the code it affects. Rank by interest rate, with debt in high-churn, high-complexity files first.
  2. Fold most repayment into feature work: when a feature touches a weak area, refactor that area as part of the feature. Also agree with product on a standing share of team capacity for debt, set as a team decision rather than ad hoc.
  3. Leave stable, rarely touched code alone even if it is ugly, and prioritise debt that blocks upcoming roadmap work or keeps causing incidents.
  4. Argue in delivery terms rather than taste: show lead time, defect rate or incident count for the affected area before the work, and measure them again after.
  5. Stop new debt from building up silently: a definition of done that includes tests, and a short decision record for every deliberate shortcut, with an owner and a condition that triggers revisiting it.

Follow-up

  • Product wants every sprint spent on features. How do you make the case for debt work?
  • Give an example of debt you consciously chose not to pay down.
  • How would you show that a refactor actually paid off?

How would you decide whether to refactor a legacy system or build a new one?

Medium · Problem-Solving / Case Studies

Approach

  1. Assess the system before choosing: is it working and earning revenue, what exactly hurts (speed of change, scaling limits, an unsupported runtime, security exposure, hiring), and is the business logic documented anywhere besides the code itself?
  2. Default to incremental change. A rewrite risks losing behaviour nobody documented, means maintaining two systems in parallel and often freezes features. The strangler fig pattern routes slices of traffic through a facade to new components and retires the old system piece by piece.
  3. Say when a rewrite is justified: the platform is end-of-life, the architecture cannot meet a hard requirement, or the component is small enough to replace quickly with low risk.
  4. Make either path safe: characterization tests that pin current behaviour, shadow traffic or dual runs that compare old and new outputs, feature flags for the cutover, and a data migration with backfill and reconciliation checks.
  5. Decide on evidence: estimate the cost and risk of both options, define success metrics, and set kill criteria for stopping or reversing course.

Follow-up

  • How would you discover behaviour nobody documented before replacing a module?
  • How do you migrate the data while the old and new systems are both live?
  • What would make you abandon a rewrite halfway through?

What metrics would you use to evaluate the success of a software project?

Medium · Problem-Solving / Case Studies

Approach

  1. Measure in layers. Outcomes: adoption, task completion, conversion, retention, support ticket volume. Delivery, using the DORA metrics: deployment frequency, lead time for changes, change failure rate, time to restore service. Reliability: SLO attainment, error rate, p95 and p99 latency, escaped defects.
  2. Define success before the work starts: a baseline, a target tied to the project's goal, and a named source and owner for each metric.
  3. Pair metrics so they cannot be gamed (Goodhart's law): velocity with defect rate, speed with change failure rate. Never use lines of code or story points as a productivity score.
  4. Use early indicators such as activation or error budget burn alongside lagging ones such as retention or revenue, and review them at set points after launch.
  5. Include cost and sustainability: infrastructure cost per request or per user, on-call load, and how easily the team can keep changing the system.

Follow-up

  • If you could track only one metric for a feature launch, which would it be, and what would you pair it with?
  • The project shipped on time but adoption is low. Was it a success?
  • How would you attribute a change in conversion to your release rather than to seasonality?

How would you approach debugging a system that has performance issues?

Medium · Problem-Solving / Case Studies

Approach

  1. Quantify before forming a theory: which metric got worse (p50, p95 or p99 latency, throughput, CPU, memory), since when, and for which endpoints or users. Then check whether it lines up with a deploy, a traffic change, data growth or a slow dependency.
  2. Narrow it down systematically: the USE method (utilization, saturation, errors) for hosts and pools, the RED method (rate, errors, duration) for services, and distributed traces to find which span dominates the slow requests.
  3. Profile the component you narrowed it to: a CPU flame graph (async-profiler for Java, py-spy for Python), GC logs and heap dumps for memory pressure, thread dumps for lock contention or exhausted connection or thread pools.
  4. Check the database and I/O culprits: the slow query log, EXPLAIN ANALYZE for sequential scans or a missing index, ORM N+1 query patterns, lock waits, and outbound calls that have no timeout.
  5. Change one variable at a time and measure against the baseline under the same load. Then add an alert or a performance test so the regression cannot come back unnoticed.

Follow-up

  • p99 latency rose while p50 stayed flat. What does that point to?
  • CPU is low but requests are slow. Where do you look next?
  • How do you confirm the fix in production and not only in a load test?

Suggested practice checklist

The plan follows the three reported stages: coding fundamentals first, then the reported design and problem-solving questions, then a hands-on rehearsal and behavioral stories.

Day 1 — Map the loop and choose your stack

  • Write out the three reported stages and, for each, what you still need to learn from the recruiter.
  • Choose one language from the role's list (Java, Python or JavaScript) and set up a project where you can run code and tests in a minute.
  • Write a short account of your last two projects and a list of questions for the Recruiter Call.

Deliverable: A one-page loop map, a chosen language with a runnable project, a project summary, and a question list for the recruiter.

Day 2 — Linked list, binary search and palindromes

  • Implement reversing a linked list iteratively and recursively, and test empty, one-node and two-node inputs.
  • Implement binary search, then the variant that finds the first occurrence of a duplicate, and test missing targets.
  • Write the palindrome check with two pointers, then handle a version that skips punctuation and ignores case.

Deliverable: Three working functions in your chosen language, each with a test file covering its edge cases.

Day 3 — Sorting and dynamic programming

  • Implement merge sort and quicksort, and write down time, space and stability for each, plus when you would choose counting or built-in sort.
  • Solve a sorting problem with a stated complexity target, and say which constraint rules out the simple approach.
  • Solve two dynamic programming problems (for example coin change and a grid path) and write the recurrence, base case and table for each.

Deliverable: Two sort implementations with a complexity table, one constrained-sort solution, and two dynamic programming solutions with recurrences.

Day 4 — Engineering fundamentals out loud

  • Write a one-page contrast of object-oriented and functional programming using examples in your language.
  • Write a comparison of REST and SOAP covering contract, payload format, statelessness and tooling.
  • Choose a real design pattern from your own work and rehearse its problem, alternative and result.
  • Prepare a list of what you do for code quality and the case for and against microservices.

Deliverable: Four spoken answers recorded and reviewed, plus a one-page cheat sheet of contrasts and trade-offs.

Day 5 — System design practice

  • Design a URL shortener end to end: requirements, API, key generation, storage, caching, redirects and failure.
  • Outline a scalable chat application: connections, message delivery, storage, ordering and presence.
  • Write short plans for a high-availability system, for data consistency in distributed systems, and for user authentication in a web application.

Deliverable: Two full designs with diagrams and three one-paragraph plans, each naming at least one trade-off.

Day 6 — Problem-solving scenarios and SQL

  • Write a debugging sequence for a system with performance problems: measure, narrow, hypothesize, fix, verify.
  • Write the steps you would take for a serious bug found just before a release, and a decision framework for technical debt, refactor versus rewrite, and project metrics.

Deliverable: A debugging runbook, a release-bug checklist, a refactor-versus-rewrite framework and a metrics list, plus the two SQL drills completed.

Day 7 — Hands-on rehearsal and behavioral stories

  • Build a small feature in your chosen stack with tests and a README, and write down how you would deploy it.
  • Write five stories in which you personally decided: a challenge, a prioritization call, a persuasion, a piece of feedback and your quality habits.
  • Run a full mock: one coding problem, one design, and two behavioral questions, out loud.

Deliverable: A tested small project with a README, five written stories, and notes from one full spoken mock.

Stories for the Canterr behavioral questions

The reported behavioral questions cover a project challenge, prioritizing across several projects, persuading others, handling feedback and what drives your quality. Candidates also describe collaboration and leadership as criteria, and the role itself involves code reviews and work with product managers and designers. Prepare stories in which you made decisions, and keep technical specifics in them.

  1. Tell me about a defect that reached users and what you changed afterwards.

Describe a time you faced a significant challenge on a project. How did you handle it?

Medium · Behavioral / Leadership

Approach

  1. Choose a challenge where you made the decision, with real stakes: a production performance regression, a migration, or a release at risk. A story where you only observed does not answer the question.
  2. Open with the situation in two sentences, then spend most of the answer on the options you weighed and why you chose one (what you gave up, what you protected).
  3. Describe your own actions in the first person: what you measured, what you changed, who you brought in, and how you checked it worked.
  4. Close with a concrete result (latency, error rate, defects, delivery date) and one thing you would do differently.

Follow-up

  • What would you do differently if you faced the same problem again?
  • How did you know your change, and not something else, fixed it?
  • What was the hardest trade-off you made along the way?

How do you prioritize tasks when managing multiple projects?

Medium · Behavioral / Leadership

Approach

  1. Show a repeatable method instead of a tool name. Start by listing everything on your plate, then sort by who is blocked, what the deadline is, and how costly a delay is.
  2. Use a real example with different kinds of work competing: a production defect, a feature deadline and a code review someone is waiting on. Say what ran first and why.
  3. Say how you communicate the trade-off to the product manager or teammate affected and what you agreed to defer, so the priority is visible and shared.
  4. Explain how you re-plan when something new arrives mid-week: you compare it against the current list instead of silently adding it, and you state what slips.
  5. Avoid claiming you finish everything. The strong answer shows you can drop or delay work deliberately.

Follow-up

  • Two stakeholders both say their request is urgent. What do you do?
  • How do you handle a new request that lands in the middle of a sprint?
  • Tell me about something you chose not to do and how that turned out.

Can you give an example of a time you had to persuade others to adopt your point of view?

Medium · Behavioral / Leadership

Approach

  1. Pick a case where you changed a decision with evidence: for example pushing for tests on a fragile module, rejecting an unnecessary rewrite, or choosing a simpler design.
  2. Begin by stating the other side's position fairly and why it was reasonable. A story that casts colleagues as obstacles reads as poor collaboration.
  3. Show how you built the case: a benchmark, a prototype, incident data or a small experiment, and how you adapted your proposal after hearing objections.
  4. Give the outcome honestly, including a partial win or a compromise, and say what you learned about influencing without authority.

Follow-up

  • What if the person still disagreed after seeing your evidence?
  • Has anyone persuaded you to change your own view, and how?
  • How would you handle the same situation with someone more senior than you?

How do you handle feedback and criticism?

Medium · Behavioral / Leadership

Approach

  1. Choose a specific piece of critical feedback, ideally from a code review or a retrospective, not a disguised strength such as working too hard.
  2. Say what the feedback was, your honest first reaction, and the point at which you decided it was right (or partly right) after checking it.
  3. Describe the change you made and the evidence it stuck, such as fewer review comments of that kind, a new habit, or a checklist.
  4. Show that you also give feedback well: one sentence on how you phrase review comments so they are specific and actionable.
  5. The mistake that sinks this answer is defensiveness or vagueness: a story with no concrete change shows no learning.

Follow-up

  • Tell me about feedback you disagreed with and what you did.
  • What is the most recent piece of feedback you acted on?
  • How do you give critical feedback to a peer?

What motivates you to deliver high-quality work?

Medium · Behavioral / Leadership

Approach

  1. Answer with behaviors, not slogans. Name the habits that show you care about quality: tests before you merge, readable code, reviewing your own diff, watching the release after it ships.
  2. Tell one short story where quality paid off (a defect caught in review, an outage avoided) or where skipping it cost you, and what you changed afterwards.
  3. Connect the motivation to the work the role describes: user-facing features, backend systems and integrations that other people depend on.
  4. Show a sense of proportion. Explain how you decide when something is good enough to ship, so your answer does not sound like perfectionism with no regard for delivery.

Follow-up

  • Tell me about a time you shipped something you were not fully satisfied with.
  • How do you keep quality up when a deadline is tight?
  • How do you decide what counts as good enough?