Interview guides · Accusol Technologies
Accusol Technologies Software Engineer Interview Guide
Accusol Technologies Software Engineer interview guide: four reported rounds, OOP, SQL and jQuery questions, and a 7-day plan.
The 60-second brief
Accusol Technologies is described to candidates as a company that delivers software products to clients. The Software Engineer description covers database design, application logic and system support, with some roles reaching from data integration up to the user-facing screens. In practice that means one engineer is expected to follow a feature from a table, through server code, to the page a user sees, and to help when users or internal support teams report problems.
This guide is for candidates applying as a Software Engineer at Accusol Technologies, at junior or senior level. It covers the four reported rounds, the nine reported technical questions (object-oriented programming, SQL and database work, and front-end and web technologies), a behavioral section for the face-to-face stage, and a seven-day plan. The language and tool requirements candidates describe are C#, JavaScript, jQuery and SQL with database design.
Candidates report four rounds over roughly 3-5 weeks: an Aptitude Assessment, a Technical Assessment, Face-to-Face Discussions with senior engineers, and an HR and Salary Discussion.
Focus areas
- Object-Oriented Programming (OOP)
- CRUD Operations (Insert/Update/Delete)
- Polymorphism
Overview
The reported questions cluster in three areas. The first is object-oriented programming: overriding versus overloading, abstraction versus interfaces, and polymorphism in a real-world scenario. The second is database work: basic create, read, update and delete statements, query optimization, and how data travels from a database into a front-end component. The third is the browser side: jQuery versus vanilla JavaScript, jQuery selectors, and the role of web services between client and back end. The requirements candidates describe name C#, JavaScript, jQuery and SQL, so examples in those languages are the natural choice.
These nine questions are broad definition-plus-example questions rather than puzzles. A bare definition is the weakest possible answer to any of them. A strong answer gives the rule, a small piece of code or a query that shows it, the trade-off against the alternative, and one case where the rule bites. Candidate tips on this loop point the same way: explain why you chose a design, not only what you wrote.
Two other parts of the loop deserve their own time. The first round is an aptitude assessment of logical reasoning, and candidates describe it as a gate before the technical stages, so it should not be left to chance. The last round is a salary conversation with HR, which rewards having your own expectations and constraints written down beforehand.
The day-to-day duties candidates read about include design and database modelling, coding and testing, code review, and troubleshooting issues raised by users or support teams. That is why the plan includes a refactoring story, a slow-query story and a debugging story from your own history, in addition to the definitions.
The Accusol Technologies interview process
Candidates describe four stages, from an aptitude test through technical work and senior-engineer discussions to an HR conversation about terms. Treat the order and content below as what candidates report, not a published schedule.
Aptitude Assessment
Candidates report that the process opens with an assessment of logical reasoning, and the role requirements add that it covers mathematical reasoning as well. It is described as a gate before the technical stages. The format is a test rather than a conversation, so preparation is mostly practice with reasoning problems and a calm approach to unfamiliar ones.
What to demonstrate
- Logical reasoning, as the round's name and description state
- Mathematical reasoning, which candidates report alongside the logical part
- Accuracy on a test that comes before any technical conversation
How to prepare
- Work through reasoning sets covering number series, percentages, ratios, simple probability and arrangement or syllogism puzzles, and keep a log of the types you get wrong.
- Practise without a calculator and decide in advance when you will skip a problem and return to it.
- Do a few sets in the same sitting so that concentration, not only method, is rehearsed.
Technical Assessment
Candidates report hands-on technical tasks meant to show coding skills and problem-solving. Candidate tips mention writing code on a whiteboard or a computer, with attention to syntax and logical flow. A coding stage is where coding and SQL practice pays off, so rehearse writing statements and functions by hand.
What to demonstrate
- Working code written during the stage, as the hands-on format implies
- Problem-solving: breaking a requirement into steps and handling edge cases
- Command of the languages the role lists, such as C#, JavaScript and SQL
How to prepare
- Write small C# classes by hand without autocomplete: a base class with a virtual method, a derived override, and an interface with two implementations.
- Write the four CRUD statements for a table you design yourself, including a parameterized INSERT and an UPDATE and DELETE with a WHERE clause.
- After each exercise, list the edge cases you skipped, such as null values, empty results and duplicate keys, and rewrite the code to cover them.
Face-to-Face Discussions
Candidates report discussions with senior members of the engineering team, covering both fit and technical knowledge. Candidate tips ask for reasoning behind choices and for stories from past projects. This is the stage where the OOP, database and web questions are most naturally talked through aloud, alongside a conversation about your experience.
What to demonstrate
- Technical knowledge, explained verbally rather than only written
- The reasoning behind design and architecture choices
- Fit with a team, including how you handle feedback and cross-team communication
How to prepare
- Rehearse the object-oriented and database questions aloud, each with a definition, a short example and a trade-off.
- Prepare two project stories in STAR form: one about speeding up a slow query and one about refactoring code toward an interface.
- Practise explaining a technical decision to a non-technical listener in a few sentences.
HR and Salary Discussion
The final stage is reported as a conversation with HR about salary and other terms of employment. No technical questions are reported here. Preparation is about knowing your own numbers and priorities before you sit down.
What to demonstrate
- Salary expectations and other employment terms, as the round's name states
- Clarity about your notice period, availability and priorities
How to prepare
- Write down your target range, your minimum and the non-salary terms that matter to you, and be ready to explain how you arrived at them.
- Prepare short, factual summaries of your experience level and the scope of your recent projects, since offers are discussed against them.
- List the questions you want answered about the role and the team before you accept.
Where candidates lose points
Defining overriding and overloading in one sentence each and stopping
Give both a code example. Overloading is chosen at compile time by the argument list; overriding is chosen at run time by the object's real type, and in C# needs virtual on the base method and override on the derived one. Add the case that trips people up: a method hidden with new does not dispatch polymorphically.
Writing UPDATE or DELETE statements without a WHERE clause, or building SQL by joining strings
Always write the WHERE clause first, use bind parameters for every value, and mention that you would run the statement inside a transaction or after a SELECT with the same condition. Name what happens when zero rows or many rows match.
Answering the query optimization question with 'add an index' and nothing else
Start from measurement: read the execution plan, find the scan or the expensive join, then choose the fix. Cover selecting only needed columns, filtering on indexed columns without wrapping them in functions, avoiding repeated queries inside loops, and the write cost of extra indexes.
Treating the jQuery questions as either 'jQuery is obsolete' or 'jQuery is the only way'
Compare concretely: $('.item') against document.querySelectorAll('.item'), how each handles an empty match, event binding, and the added file size. Say when you would keep jQuery (an existing codebase that depends on it) and when plain JavaScript is enough.
Giving correct answers with no reasoning, or skipping the first-round reasoning test
Candidate tips stress explaining why behind a choice, so finish every answer with the trade-off you weighed. Candidates also describe the aptitude stage as a gate, so give it dedicated practice sets rather than assuming reasoning problems will be easy.
Questions & practice
Collapse a redelivered event batch into per-aggregate high-water marks
Easy · hashing · at-least-once · aggregation
You drain a batch of up to 5,000,000 events, each (aggregate_id BIGINT, aggregate_version INT, event_type, payload). The log guarantees order within one aggregate only; the batch merges 64 partitions, and a relay failover has redelivered a range, so an older version for an aggregate can appear after a newer one. Given a map of last_applied_version per aggregate, produce the events worth applying, at most one per (aggregate_id, version), plus the count discarded. Target O(n) time. State the memory for 2,000,000 distinct aggregates and what you do when it does not fit.
Approach
- One pass, one hash map from aggregate_id to the highest version kept, and a discard counter. An event whose version is at or below last_applied_version for its aggregate is dropped without further work, which is the whole reason the event carries its version rather than a delta. O(n) expected time, O(d) space in distinct aggregates.
- Keep the maximum, never the last occurrence. The redelivered range means the final appearance of an aggregate in the batch can be an older version than one seen earlier in the same batch, so last-wins applies stale state over newer state and the projection regresses with no error anywhere.
- Cost the memory instead of calling it large: an 8-byte key plus a 4-byte version is 12 bytes of payload, and an open-addressed table held at a 0.7 load factor costs roughly 17 bytes per entry before per-slot metadata, so 2,000,000 aggregates is tens of megabytes in a native layout and several times that in a runtime that boxes both key and value.
- If the distinct set exceeds memory, partition on hash(aggregate_id) mod P and reduce each partition independently. Every event for one aggregate hashes to the same partition, so the per-partition result is exact and the merge is concatenation rather than a second reduction.
Follow-up
- The payload is a patch rather than a snapshot, so applying only the highest version loses the intermediate changes. What changes in your reduction?
- How do you detect that version 7 arrived while version 6 was never delivered, and what should the consumer do about the gap?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Canonicalise a request body into a stable idempotency fingerprint
Medium · parsing · canonicalisation · hashing
idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.
Approach
- Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
- Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
- Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
- Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.
Follow-up
- A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
- Where does the fingerprint get computed relative to request decompression and the body-size limit?
Write a query to perform basic CRUD (Create, Read, Update, Delete) operations.
Medium · Database and Data Management
Approach
- Write all four statements against one small table, for example Customers(Id, Name, Email): INSERT INTO Customers (Name, Email) VALUES (@name, @email); SELECT Id, Name, Email FROM Customers WHERE Id = @id; UPDATE Customers SET Email = @email WHERE Id = @id; DELETE FROM Customers WHERE Id = @id.
- Use bind parameters everywhere (SqlCommand with SqlParameter in C#), never string concatenation, so input cannot inject SQL. Name the columns instead of SELECT * so a schema change does not silently change the result.
- Return the generated key in the same statement (SQL Server: OUTPUT INSERTED.Id or SCOPE_IDENTITY(); PostgreSQL: RETURNING id). Check the rows-affected count from ExecuteNonQuery: 0 on UPDATE or DELETE means the row was not there.
- Edge cases: an UPDATE or DELETE without a WHERE clause hits every row. A DELETE of a row that other tables reference by foreign key fails unless the key has a cascade rule. Some data needs a soft delete (an IsDeleted flag) instead. Wrap multi-table writes in a transaction.
Follow-up
- Two users edit the same record at once. How do you stop the second save from silently overwriting the first (optimistic concurrency with a rowversion column in the WHERE clause)?
- Write an insert-or-update in one statement: MERGE in SQL Server, INSERT ... ON CONFLICT in PostgreSQL.
- How would you delete a customer who still has orders?
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?
Explain the difference between method overriding and method overloading.
Medium · Object-Oriented Programming (OOP)
Approach
- Overloading means one method name with different parameter lists (count, types or order). The C# compiler picks the overload at compile time from the static types of the arguments. A different return type alone is not a valid overload.
- Overriding means a derived class replaces a base member that is marked virtual or abstract, using the override keyword and the same signature. The call is resolved at runtime from the object's actual type (dynamic dispatch).
- Show the contrast in code. With Base b = new Derived(); b.Describe() runs Derived.Describe when it overrides, but runs Base.Describe if Derived only hides it with the new keyword, because hiding binds to the reference's static type.
- Edge cases: overload choice follows static argument types, so with object o = "x", Print(o) calls Print(object), not Print(string). Static methods cannot be overridden. An override cannot change the accessibility. sealed override stops further overriding.
Follow-up
- What changes if the derived method uses new instead of override, and when would hiding ever be intentional?
- Given Print(int) and Print(long), which one does a call with a short argument pick, and why?
- Can a constructor be overloaded? Can it be overridden?
How do abstraction and interfaces differ in their application?
Medium · Object-Oriented Programming (OOP)
Approach
- Abstraction is the design idea: callers depend on what a type does, not how it does it. In C#, abstract classes and interfaces are the two language tools for it, so the useful answer is when to pick each.
- An abstract class can hold instance fields, constructors, implemented members and protected helpers, and a class can inherit from only one class. It fits closely related types that share state or a fixed algorithm (template method: the base runs the steps, subclasses fill in the abstract ones).
- An interface has no instance fields, and a class can implement many of them. It fits a capability shared by unrelated types (IComparable, IDisposable) and a seam for dependency injection and test doubles, for example an IOrderRepository with a SQL-backed class and an in-memory fake.
- Trade-off: adding a member to an interface breaks every implementer unless it has a default implementation (C# 8+). Adding a concrete method to an abstract class does not break subclasses. The common pattern is an interface as the contract plus an optional abstract base class for shared code.
Follow-up
- Since interfaces can have default methods, what can an abstract class still do that an interface cannot?
- Two interfaces declare the same method name with different meanings. How does explicit interface implementation resolve that?
- Refactor a class that news up its SqlConnection directly so it can be unit-tested. Which interface would you introduce?
Can you describe how polymorphism works in a real-world scenario?
Medium · Object-Oriented Programming (OOP)
Approach
- Pick one concrete case and show the call site, for example notifications: an INotifier with EmailNotifier and SmsNotifier, and a loop that calls notifier.Send(message) on each. The caller never checks which type it holds.
- Name the mechanism: the call is dispatched at runtime on the object's actual type through virtual or interface dispatch. Adding a PushNotifier means adding one class, not editing the loop (open/closed principle).
- Show the code it replaces: a switch or if-chain on a type code that every new channel has to edit, and that silently misses a case when someone forgets one.
- Separate the kinds: subtype polymorphism (overriding, resolved at runtime), ad hoc polymorphism (overloading, resolved at compile time) and parametric polymorphism (generics such as List<T>).
- Edge case: substitutability. A subclass that throws NotSupportedException for a method its base promises breaks callers. Square deriving from a mutable Rectangle is the classic Liskov violation.
Follow-up
- How would you add a channel that needs extra configuration, such as a webhook URL, without changing the caller?
- When is a switch statement better than a class hierarchy?
- How would you unit-test the loop without sending real email?
How do you optimize database queries for better performance?
Medium · Database and Data Management
Approach
- Measure first: find the slow statements (SQL Server Query Store or the query-stats DMVs, PostgreSQL pg_stat_statements), then read the actual execution plan (EXPLAIN ANALYZE in PostgreSQL). Look for table or index scans where a seek is expected, key lookups, and row estimates far from the actual counts.
- Index for the predicate: cover the WHERE, JOIN and ORDER BY columns. In a composite index, put equality columns before range columns. Use a covering index (INCLUDE columns in SQL Server) to remove key lookups.
- Keep predicates sargable. WHERE YEAR(CreatedAt) = 2024, a leading-wildcard LIKE '%term' and implicit type conversions all block index seeks. Rewrite the first as CreatedAt >= '2024-01-01' AND CreatedAt < '2025-01-01'.
- Cut the work at the application layer too: select only the needed columns, paginate (keyset pagination stays fast on deep pages, OFFSET does not), and replace N+1 query loops with one join or a batched IN query.
- Trade-offs: every index slows INSERT, UPDATE and DELETE and uses storage. Stale statistics produce bad plans. Confirm each change with a before-and-after plan and timing, not by guessing.
Follow-up
- A query is fast in SSMS but slow from the application. What would you check (parameter sniffing, different SET options, a different plan in cache)?
- When would you decide not to add an index that would speed up a read?
- How would you speed up a report that aggregates millions of rows every time someone opens it?
Explain the process of fetching data from a database to populate a front-end UI component.
Medium · Database and Data Management
Approach
- Walk the request end to end: a UI event (page load or a filter change) fires an AJAX call ($.ajax/$.getJSON or fetch) to an HTTP endpoint. The server validates input, runs a parameterized query through its data-access layer, maps rows to a DTO, and returns JSON that the client renders.
- On the server: open the connection in a using block so it goes back to the pool, use async calls so request threads are not blocked on I/O, and return only the fields the component needs, never whole entity rows or internal columns.
- On the client: render with textContent or jQuery .text() rather than raw HTML to avoid XSS, and handle the loading, empty and error states explicitly instead of leaving a blank component.
- Scale and edge cases: page, filter and sort on the server rather than shipping the whole table. Make sure a slow, stale response cannot overwrite a newer one (cancel with AbortController or track the latest request). Check that the user is authorized for the rows asked for, not only logged in.
Follow-up
- The table grows to 100,000 rows. What changes in the query, the endpoint and the UI?
- A search box fires a request on every keystroke. How do you cut the load and keep results in order (debounce, cancel stale requests)?
- Where would you cache this data, and how would you invalidate the cache after an update?
What are the key differences between jQuery and vanilla JavaScript?
Medium · Front-End and Web Technologies
Approach
- jQuery is a library on top of the DOM APIs. $(selector) returns a jQuery wrapper around zero or more elements, while document.querySelector returns one Element (or null) and querySelectorAll returns a static NodeList. Convert with .get(0) or [0], and wrap a native element with $(el).
- jQuery uses implicit iteration and chaining: $('.row').addClass('active').show() acts on every match. Vanilla needs forEach over the NodeList, and classList, addEventListener and closest() now cover most of what jQuery wrapped.
- Its main early value was smoothing over browser differences, especially in old Internet Explorer. In current browsers the standard APIs are consistent, so jQuery is mostly an extra dependency to download and parse.
- Know the AJAX difference: fetch returns a Promise that only rejects on a network failure, so a 404 or 500 still resolves and you must check response.ok. jQuery's $.ajax runs its error callback for non-2xx responses.
- Event delegation: $(container).on('click', '.btn', fn) compared with container.addEventListener('click', e => { const b = e.target.closest('.btn'); if (b) ... }).
Follow-up
- Rewrite $('#list').on('click', 'li', handler) in vanilla JavaScript.
- Would you remove jQuery from an existing application? What would decide it?
- Why does a fetch call to a missing URL not land in the catch block?
How do you effectively use jQuery selectors to manipulate the DOM?
Medium · Front-End and Web Technologies
Approach
- Selectors follow CSS syntax: $('#id'), $('.class'), $('input[name="email"]'), $('ul > li'), plus pseudo-selectors such as :checked. Note that jQuery-only extensions like :visible cannot use the native querySelectorAll path and are slower. :eq() and :first are deprecated as of jQuery 3.4 in favour of .eq() and .first().
- Performance habits: an ID selector is the fastest lookup. Cache a selection in a variable instead of re-querying inside a loop. Scope searches with $container.find('.item'). When inserting many rows, build the markup or a fragment and append it once instead of appending inside the loop.
- Pick the right manipulation method: .text() escapes and .html() parses (an XSS risk with user data). .val() reads form values. .prop('checked') gives the current state, while .attr('checked') gives the initial HTML attribute. Use .addClass/.toggleClass, .append, .remove and .empty for structure.
- Edge cases: an empty selection fails silently, so check .length when it matters. Handlers bound with .click() only attach to elements that exist at bind time; use delegation via .on(event, selector, fn) on a stable parent for rows added later by AJAX. Escape special characters in IDs with $.escapeSelector (jQuery 3.0+).
Follow-up
- Rows loaded by AJAX do not respond to a click handler that works on the first page. Why, and what is the fix?
- What do .attr('checked') and .prop('checked') return after the user ticks a checkbox?
- How would you select an element whose id is user.name?
Explain the role of web services in connecting client applications to back-end data.
Medium · Front-End and Web Technologies
Approach
- A web service is the HTTP layer between the client and the database. The browser never holds database credentials or opens a connection. The service authenticates the caller, validates input, applies business rules, runs the queries and returns a serialized result, usually JSON.
- Map REST onto CRUD: resource URLs with GET (read), POST (create, 201 Created with a Location header), PUT or PATCH (update) and DELETE. Return correct status codes: 400 for bad input, 401 when not authenticated, 403 when authenticated but not allowed, 404, 409 for conflicts, 500 for server faults.
- Contrast with SOAP, which is still common in older .NET systems (ASMX, WCF): an XML envelope, a WSDL contract and WS-* standards, with stricter typing and more overhead than JSON over REST.
- Cross-cutting concerns: statelessness so instances can scale horizontally, paging for list endpoints, CORS when the client is on another origin, caching with ETag or Cache-Control, and versioning (/v2 or a header) so existing clients keep working.
- Idempotency: GET, PUT and DELETE are idempotent by definition and POST is not, so a retried POST needs an idempotency key to avoid creating duplicates.
Follow-up
- What is the difference between 401 and 403, and which does an expired login return?
- A mobile client retries a POST after a timeout and creates two orders. How do you prevent that?
- How would you change a response field without breaking clients that still read the old one?
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
The plan follows the reported order: reasoning practice for the first round, then OOP, SQL, query performance and web topics for the technical stages, and finally stories and a pay conversation for the last two rounds.
Day 1 — Map the four rounds and start reasoning practice
- Write the four reported rounds on one page, and next to each, what you will do to prepare for it.
- Complete two sets of logical and mathematical reasoning problems without a calculator and mark every miss by type (series, ratios, percentages, arrangements).
- Plan how the next six days fit around your weakest area.
Deliverable: A one-page round map and an error log listing the reasoning problem types you missed.
Day 2 — Overriding, overloading, abstraction and interfaces
- Write a C# example with a virtual method overridden in a derived class and an overloaded method with two signatures, and state which choice is made at compile time and which at run time.
- Write an abstract class and an interface for the same scenario, and list what each can hold (state, constructors, default behavior, multiple inheritance).
- Say aloud your rule for choosing between them, with one example where each is the better choice.
Deliverable: One compiled C# file with both examples, plus a written rule for choosing an abstract class or an interface.
Day 3 — Polymorphism in a real scenario and refactoring to an interface
- Pick a scenario you can explain in one minute, such as several payment or report types behind one interface, and write the code with a switch statement first and the polymorphic version second.
- Describe the refactoring steps from a class that hard-codes its dependency to one that takes an interface, and what that change makes testable.
- Record yourself explaining polymorphism using the scenario, without reading from notes.
Deliverable: A before-and-after code pair and a one-minute spoken explanation of polymorphism built on it.
Day 4 — CRUD statements and table design
- Design a two-table schema with a primary key, a foreign key and a unique constraint, and write the CREATE TABLE statements.
- Write parameterized INSERT, SELECT, UPDATE and DELETE statements for it, then write down what each does when zero rows match and when a constraint is violated.
Deliverable: A schema, four tested CRUD statements, and a note on constraint and zero-row behavior.
Day 5 — Query optimization and the path from database to UI
- Take a slow query on a table you can create locally, read its execution plan, add an index, and compare plans before and after.
- Write the end-to-end path for populating a front-end component: query, server code or web service, JSON response, client rendering, and where you would handle errors, loading state and paging.
Deliverable: Before and after execution plans with a written explanation, and a labelled diagram of the data path from table to component.
Day 6 — jQuery, vanilla JavaScript and web services
- Rewrite five jQuery snippets (a selector with a class and child, an attribute filter, an event handler, an AJAX call, and a show/hide) in vanilla JavaScript, and compare them.
- Write the selector rules you rely on: ID, class, descendant, child, attribute and filter selectors, and how to cache a selection instead of repeating it.
- Explain what a web service does between a browser and a database, including request, validation, query, serialization and response codes.
Deliverable: A side-by-side jQuery and vanilla JavaScript cheat sheet, and a one-paragraph description of a web service's role.
Day 7 — Stories, mock run and the HR conversation
- Write two STAR stories: one on a slow query you improved and one on legacy code you made more modular, each with the decision you made and the result.
- Run a full mock: reasoning set, one handwritten coding task, then three of the reported questions answered aloud.
- Write your salary range, your minimum, your priorities among other terms, and questions about the role.
Deliverable: Two written STAR stories, a mock-run scorecard listing weak answers to redo, and a one-page note for the HR discussion.
Behavioural preparation
The face-to-face stage is described as a discussion with senior engineers about fit and technical knowledge, and the candidate tips ask for the reasoning behind your choices and for details from your past projects. Prepare stories where you made a technical decision, say why, and say what happened. Candidates are also advised to use the STAR structure and to explain how they would find an answer when they do not know it.
- How would you refactor this legacy code to use an interface for better modularity?
- Describe a time you had to optimize a slow-performing query.
- Walk through a past project and the specific technical decisions you made to overcome its biggest challenge.
- Tell me about a time you did not know the answer to a technical problem. How did you find the solution?
- Describe a time you worked with people from other engineering disciplines and how you kept communication clear.
- Tell me about feedback on your code from a review and what you changed because of it.
Argue against a design, lose, and commit anyway
Medium · disagreement · service boundaries · decision records
Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.
Approach
- State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
- Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
- Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
- Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.
Follow-up
- What threshold on that alert would have proved you right, and did anyone ever look at it?
- If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?
Unblock an engineer without taking the keyboard
Easy · mentoring · leases · at-least-once
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
Approach
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
Follow-up
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?
Ship under a deadline and bound the debt you chose
Medium · pagination · technical debt · tradeoffs
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
Approach
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
Follow-up
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?