Interview guides · Anz
Anz Software Engineer Interview Guide
Anz Software Engineer interview guide: recruiter screen, technical assessments, software design round, reported questions and a 7-day prep plan.
The 60-second brief
Anz is a bank. Candidates describe Software Engineers there as people who build, maintain and scale the systems behind its financial services, with work reported across the core banking infrastructure and the ANZx (ANZ Plus) platform. Day to day, that means taking part in the full development lifecycle: gathering requirements, designing, testing and deploying to production. Candidates also describe working with product managers on feature scope, with designers and other engineers, and with operations and security teams so that code meets the bank's compliance and security requirements. Code reviews and team discussions are part of the job, and the reported requirements stress documentation and process alongside engineering skill.
This guide is for candidates applying for a Software Engineer role at Anz. It covers the three reported stages, the reported technical and behavioral questions, and a one-week plan built around them. The languages candidates list for the role are Java, C# and Python (JavaScript is also mentioned, depending on the team), together with SQL and Git; AWS or Azure, CI/CD and banking or fintech experience are listed as nice to have. The guide does not cover other positions at Anz.
Candidates report three stages over roughly 3-5 weeks: a recruiter screening, a set of technical assessments (online coding tests and live coding sessions), and a software design interview.
Focus areas
- DSA (Data Structures & Algorithms)
- Coding Interviews / Problem Solving
- System Design
Overview
The reported questions fall into three groups. The first is applied computer science with a banking flavour: designing for high-concurrency transactions, comparing microservices with a monolith, securing a public-facing web API, keeping data consistent in a distributed system, and managing infrastructure as code for a cloud deployment. The second is hands-on coding: finding duplicate accounts in a large customer dataset, writing a script that processes and validates batches of financial records, a medium array or hash map problem, optimising search over a large database, and structuring classes and interfaces for maintainable code. The third is behavioral: team conflict, ambiguous or shifting requirements, technical debt against delivery speed, quality and security standards, and explaining technical ideas to non-technical people.
Money is the common thread, and it shapes how to answer. A banking answer should say what happens when a request is retried, when two writers touch the same account, when a record in a batch is malformed, and when a downstream service is unavailable. Practise saying the failure case before the happy path: idempotency keys, version checks or row locks, transactions that keep a ledger balanced, rejected records that are reported rather than dropped, and amounts held as integers or decimals rather than floats.
Candidates report that the reasoning matters more than a perfect final answer, and that coding questions reward narrating assumptions and trade-offs. Treat that as a preparation rule: restate the problem, ask what counts as a duplicate or an invalid record, give the simple solution and its cost, then improve it. Design prompts are reported as deliberately vague, so rehearse scoping questions you would ask before drawing any boxes.
Because the reported requirements stress version control, SQL and one major language, pick one language from Java, C# or Python and do all your coding practice in it, so the live sessions are not also a fight with syntax. Add enough SQL to reason about indexes, transactions and constraints, since several design questions (consistency, search optimisation, duplicate detection) come down to what the database can enforce.
The Anz interview process
These are the stages candidates report, in the order they report them. The mix of online tests and live sessions can vary by team, so confirm the format with your recruiter.
Recruiter Screening
Candidates report a conversation with a recruiter about your experience, your salary expectations and your interest in the role. Nothing technical is described for this stage, but it is where you learn which team you are interviewing for, which language and stack that team uses, and what the technical assessments will look like. Go in with a clear account of your recent work, a reason for choosing a bank and, if relevant, the ANZx platform, and a salary figure you can state calmly.
What to demonstrate
- How your experience maps to the role and its Java, C# or Python, SQL and Git requirements
- Your stated interest in the role and the team
- Whether your salary expectations fit
How to prepare
- Write a short walkthrough of your last two projects: what you built, your part in it, the language and database, and one outcome you can state plainly.
- Prepare a specific reason for applying to a bank, and a view on whether the traditional banking systems or the ANZx platform interests you more.
- Decide your salary range and availability before the call, and ask which online test platform is used and whether the live sessions are in a language you choose.
Technical Assessments
Candidates report a series of technical assessments: online coding tests (HackerRank and Pymetrics are the names mentioned) and back-to-back live coding sessions. A coding stage is where the reported coding questions are practised: a medium array or hash map problem, finding duplicate accounts, validating batches of financial records, and searching a large dataset efficiently. Candidates say the interviewer cares about how you think, so talk through your approach before typing.
What to demonstrate
- Working code in your chosen language on data structure problems
- Choosing a data structure from the access pattern and stating its complexity
- Explaining your reasoning and trade-offs while you code
How to prepare
- Solve medium array and hash map problems in one language until frequency maps, seen-sets, two pointers and prefix sums are automatic, and say the brute-force complexity aloud each time.
- Write the duplicate-accounts function and the batch validator end to end, including the edge cases: empty input, one record, all records invalid, and input too large for memory.
- Practise in a plain online editor without autocomplete, since online tests do not offer your IDE.
Software Design Interview
The last reported stage is a software design interview. The reported design-style questions include high-concurrency banking transactions, microservices against a monolith, securing a public-facing web API, data consistency across a distributed system, infrastructure as code for a cloud deployment, and a feature that must handle 10,000 requests per second. Candidates also say requirements can be vague on purpose, so start by asking questions to scope the problem.
What to demonstrate
- Designing systems that are scalable, reliable and secure
- Stating trade-offs between consistency, availability and complexity
- Scoping an ambiguous problem with clarifying questions before designing
How to prepare
- Build one reusable design skeleton: clarify requirements and load, sketch the API and data model, then cover the write path, the read path, failure handling, security and deployment.
- Rehearse the money-specific parts: idempotency keys on retries, per-account locking or versioning, an outbox for events, and reconciliation between systems.
- Prepare a clear answer on microservices against a monolith for a bank, and a walk through one request across gateway, authentication, services, database and events.
- Know how you would manage infrastructure as code on AWS or Azure: version-controlled definitions, review, environments, state, and a CI/CD pipeline that applies them.
Where candidates lose points
Writing the duplicate-accounts function before defining what a duplicate is
Ask whether duplicates are exact matches or near matches, and which fields identify a customer. Then normalise fields into a key (case, whitespace, punctuation), group with a hash map, and say how you would add a fuzzy-matching pass for near misses. Report the groups instead of deleting anything.
A batch validator that handles only the clean file, stops at the first bad record, or holds amounts as floating-point numbers
Collect every error with its record number, write valid and rejected records to separate outputs, store money as integer minor units or decimals, stream the file instead of loading it, and make a rerun safe so a retried batch cannot post twice.
A high-concurrency or consistency design with no answer for retries, two writers on one account, or a downstream outage
Put an idempotency key on every write, choose row locking or a version check for hot accounts, publish events through an outbox, and say what the caller sees in each failure. Name which data must be strongly consistent and which may lag.
Answering the API security question as a list of buzzwords such as HTTPS and OAuth
Order the answer by threat: authenticate the caller, authorise each object access, validate input, rate-limit and log, protect secrets, encrypt in transit and at rest. Give one concrete attack for each layer so the list shows reasoning.
Coding silently, or behavioral answers told as 'we' with no personal decision or result
Narrate assumptions and trade-offs while you code, as candidates are advised to do. For stories, use Situation, Task, Action and Result, say 'I' when describing your actions, and finish with a result you can describe.
Questions & practice
Write a script to process and validate incoming financial record batches.
Medium · Coding and Algorithms
Approach
- Ask for the format and the contract: file type (CSV, fixed-width, XML), whether a header and trailer carry a record count and control total, and whether one bad record rejects the whole batch or is quarantined while the rest proceed.
- Stream the file record by record instead of loading it into memory, parse each line against a schema, and keep amounts as exact decimals (BigDecimal, C# decimal, Python decimal.Decimal), never binary floats.
- Validate each record: required fields present, types and lengths, currency as a valid ISO 4217 code, dates real and in range, amount sign and precision, account number format; collect every error with its line number instead of stopping at the first.
- Validate the batch: record count and summed amounts must match the trailer, transaction ids must be unique within the batch (a set) and against previously processed ids, and the batch id itself is recorded so a re-delivered file is not processed twice.
- Write accepted records in one transaction or in idempotent chunks, emit a report of accepted and rejected rows, and state the cost: O(n) time and memory proportional to the distinct ids tracked.
Follow-up
- The file is 50 GB and duplicate checks must span the last 30 days of batches. How does your design change?
- The process crashes after writing half the records. What happens when it restarts?
- Which test cases would you write first for this script?
Solve a medium-level LeetCode problem focusing on data structures like arrays or hash maps.
Medium · Coding and Algorithms
Approach
- Restate the constraints aloud (input size, negatives, duplicates, sorted or not), then name the brute force and its complexity before improving it, as the reported question is a generic medium array or hash-map problem.
- Know the core hash-map patterns: value-to-index map for pair sums in one pass, O(n) time and O(n) space; frequency counts for anagram and majority problems; grouping by a canonical key (sorted string or a 26-count tuple) for group-anagrams.
- Know prefix sum plus hash map: to count subarrays summing to k, store counts of prefix sums seen and add count[prefix - k] at each step, seeding count[0] = 1. It is O(n) and, unlike a sliding window, works with negative numbers.
- Know sliding window plus map or set for substring problems such as longest substring without repeating characters, moving the left pointer past the last seen index, O(n).
- Test edge cases explicitly: empty input, one element, all duplicates, negatives and zero, integer overflow in Java or C#; note hash operations are O(1) on average but can degrade with heavy collisions.
Follow-up
- The array is sorted. Can you solve the pair-sum version in O(1) extra space?
- Return every unique pair rather than one pair: how do you avoid duplicate pairs?
- The numbers arrive as a stream too large to store. What can you still compute exactly?
Describe how you would optimize a search algorithm for a large-scale database.
Medium · Coding and Algorithms
Approach
- Measure before changing anything: capture the slow query and its parameters, run EXPLAIN ANALYZE, and find whether the time goes to a sequential scan, a sort, a large join or fetching many rows.
- Fix access paths with indexes: a B-tree for equality, range and ORDER BY; in a composite index put equality columns before the range column; use a covering index so the query is answered from the index alone. Each index slows writes and costs storage, so add only what the queries use.
- Match the index type to the search: a pattern that starts with a wildcard (LIKE '%smith') cannot use a B-tree, so use a trigram GIN index (pg_trgm in PostgreSQL), full-text search with a tsvector GIN index, or a dedicated engine such as OpenSearch or Elasticsearch for ranking and typo tolerance.
- Fix the query shape: select only needed columns, avoid wrapping indexed columns in functions, replace OFFSET pagination with keyset pagination (WHERE (created_at, id) < (last values) ORDER BY ... LIMIT n), and partition very large tables by date so queries prune partitions.
- Then scale reads: cache frequent lookups with explicit invalidation, and send read traffic to replicas while noting replication lag means a customer may not see a write they just made.
Follow-up
- An index exists on the column but the planner still does a sequential scan. What are the likely reasons?
- Customers misspell names when searching. How do you support approximate matching?
- Search now spans several shards. How do you merge and paginate the results?
Given a specific scenario, how would you structure classes and interfaces to ensure maintainable code?
Medium · Coding and Algorithms
Approach
- Read the scenario for nouns and verbs: nouns become candidate classes or value objects, verbs become methods, and anything with several interchangeable variants (fee rules, payment methods, notification channels) becomes an interface.
- Apply the principles concretely: single responsibility per class, a strategy interface so a new variant is a new class rather than an edited if/else chain (open/closed), and dependencies passed through constructors so tests can substitute fakes (dependency inversion).
- Prefer composition over inheritance unless there is a true is-a relationship with shared behaviour, and make domain values immutable, for example a Money type holding amount and currency that refuses to add different currencies.
- Keep domain logic separate from I/O: the service depends on a repository interface, not on the database client, which keeps the core unit-testable.
- Avoid speculative abstraction: introduce an interface where a second implementation or a test double is actually needed, and say out loud where you chose not to abstract.
Follow-up
- A new account type needs a different fee rule. Show what changes and what does not.
- How would you unit test the service class without a database?
- Where would inheritance be the better choice in this scenario, and why?
How do you approach a situation where requirements are ambiguous or constantly changing?
Medium · Behavioral and Leadership
Approach
- Turn ambiguity into checkable statements: ask who the user is, what problem it solves and what 'done' looks like, then write acceptance criteria with concrete examples, including the failure and edge cases.
- Write down every assumption you make and get it confirmed in writing, so a later change is visible as a change rather than a misunderstanding.
- Separate the stable core from the volatile parts and design for the volatile ones: put them behind an interface, configuration or a feature flag, and version any external API contract so a change does not break callers.
- Ship a thin end-to-end slice early and collect feedback on working software rather than on a long specification, then iterate in small increments.
- Back it with one real example: the unclear requirement, the questions you asked, what you built first, what changed and how the design absorbed it. Avoid the two answers that fail: building everything speculatively, or refusing to start until the spec is perfect.
Follow-up
- A stakeholder changes a requirement after release. How do you decide whether to absorb it now or push back?
- How do you decide you have asked enough questions and should start building?
- How do you handle scope creep when the deadline does not move?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
How would you design a system to handle high-concurrency banking transactions?
Medium · Technical and Domain Knowledge
Approach
- Pin down the operations first (transfers between accounts, card debits, balance reads) and the guarantees: money is never created or lost, and a retried request never applies twice. Write those two invariants on the board before drawing anything.
- Model money as an append-only double-entry ledger: every transfer writes a debit and a credit row in one database transaction, and a balance is the sum of entries (optionally cached in a balance row updated in that same transaction).
- Make every write idempotent: the client sends an idempotency key, stored under a unique constraint alongside the ledger entries, so a retry after a timeout returns the original result instead of posting again.
- Handle concurrency on a balance with a row lock (SELECT ... FOR UPDATE) or an optimistic version check; for a transfer, lock both accounts in ascending account-id order so two opposite transfers cannot deadlock. Reject the debit if it would breach the overdraft rule inside the locked section.
- Scale by partitioning on account id so most transfers touch one shard; name the cost: cross-shard transfers need a saga or two-phase commit, and a very hot account (a large merchant) serialises on its row, which you can relieve by splitting it into sub-ledgers or queueing its postings.
Follow-up
- The client times out and retries a transfer: walk through exactly what each component does on the second request.
- One merchant account receives thousands of credits per second. What breaks, and how do you fix it without losing correctness?
- How do downstream systems (notifications, fraud checks) learn about a committed transfer without a dual write?
Explain the difference between microservices and monolith architecture in a banking context.
Medium · Technical and Domain Knowledge
Approach
- Define both precisely: a monolith is one deployable with in-process calls and usually one database, so a single ACID transaction can span modules; microservices are independently deployed services that own their data and talk over the network.
- Weigh what each costs: microservices give independent deploys, scaling and team ownership, but add network failure, latency, versioned contracts, distributed tracing and no cross-service transactions; a monolith is simpler to test and reason about but couples release cycles and scaling.
- Apply it to banking: an operation such as posting a transfer needs atomicity, so keep the ledger and balance logic inside one service boundary; flows that span services (account opening, payments plus notifications) become sagas with compensating steps and idempotent handlers.
- Mention the middle ground and the migration path: a modular monolith with enforced module boundaries, or the strangler-fig pattern that routes one capability at a time from a legacy core system to new services.
- Close with a criterion rather than a preference: split where the domain boundary, the team boundary and the scaling need line up, and keep together what must change or commit together.
Follow-up
- A transfer now spans an accounts service and a payments service. How do you keep it consistent if the second step fails?
- How would you decide where to draw the first service boundary when breaking up an existing core system?
- How do you trace a single customer request that crosses five services when something fails?
What are the key considerations when implementing security in a public-facing web API?
Medium · Technical and Domain Knowledge
Approach
- Start with transport and identity: TLS on every endpoint (with HSTS for browser clients), OAuth 2.0 / OpenID Connect for authentication, short-lived access tokens, and full JWT validation of signature, issuer, audience and expiry, rejecting the 'none' algorithm.
- Separate authentication from authorisation: check on every request that the caller may act on that specific object (account id in the path belongs to the token's subject). Broken object-level authorisation tops the OWASP API Security Top 10 for a reason.
- Validate all input against a schema (types, lengths, ranges, allowed values), use parameterised queries or bind parameters for SQL, and return errors that do not leak stack traces or whether an account exists.
- Limit abuse: rate limits per client and per credential returning 429, lockout or step-up checks against credential stuffing, idempotency keys on money-moving POSTs, and request size limits.
- Cover operations: secrets in a secrets manager rather than code or config, masking card numbers and personal data in logs, an audit trail of who did what, dependency scanning, and a note that CORS restricts browsers only and is not an authorisation control.
Follow-up
- JWTs are stateless. How do you revoke a token for a customer who reports their phone stolen?
- How would you secure service-to-service calls behind the public gateway?
- How would you detect and slow down a credential-stuffing attack without locking out real customers?
Explain how you would manage infrastructure as code (IaC) for a cloud-based deployment.
Medium · Technical and Domain Knowledge
Approach
- Pick a declarative tool and say why: Terraform, AWS CloudFormation or CDK describe the desired state, and the tool computes the diff, so the same code produces the same environment every time.
- Structure the code as reusable modules (network, database, service) with one root configuration per environment, and keep state remote with locking (for Terraform, an S3 backend with a lock) so two applies cannot run at once. Encrypt and restrict the state file, because it can contain secrets in plain text.
- Run changes through the same review as application code: a pull request shows the plan output, policy checks run in CI (Checkov, tfsec or OPA-style rules for public buckets, open security groups, unencrypted storage), and only the pipeline holds apply credentials.
- Keep secrets out of the repository by referencing a secrets manager at deploy time, and promote one tested version of the modules from dev to staging to production instead of hand-editing each environment.
- Handle drift and rollback: scheduled plans detect console changes, and rollback means reverting the commit and applying again, with destructive changes (database replacement) guarded by prevent_destroy-style lifecycle rules and backups.
Follow-up
- Someone changed a security group by hand in the console during an incident. How do you find out, and what do you do with the change?
- An apply fails halfway through. What state is the environment in, and how do you recover?
- How would you give each team self-service infrastructure without giving them production apply rights?
Design a function to identify duplicate accounts within a large customer dataset.
Medium · Coding and Algorithms
Approach
- Clarify what 'duplicate' means: identical records, or the same person entered differently (name spelling, email case, phone format, moved address). Ask whether the output auto-merges or goes to a review queue, since a false merge of two real customers is worse than a missed match.
- Normalise before comparing: trim and case-fold names and emails, convert phone numbers to E.164, standardise addresses and dates. Exact duplicates then fall out of one pass with a hash map keyed on the normalised tuple, O(n) time.
- For fuzzy matches avoid the O(n^2) all-pairs comparison with blocking: group records by a cheap key (date of birth plus postcode, or a phonetic code such as Soundex of the surname) and compare only within each block using Jaro-Winkler or Levenshtein similarity per field, combined into a weighted score with a threshold.
- Cluster matches with union-find so A
B and BC put all three in one group; with path compression and union by rank each operation is near-constant amortised. - At scale the same shape runs as a distributed group-by on the blocking key (Spark, or SQL GROUP BY plus a self-join within blocks); state the trade-off that wider blocks catch more matches but cost more comparisons.
Follow-up
- Twins share a surname, date of birth and address. How does your scoring avoid merging them?
- The dataset no longer fits on one machine. What changes in your function?
- How would you check each new sign-up against existing customers in real time instead of in a nightly batch?
p99 jumped on one listing filter while p50 stayed flat
Medium · query planning · composite index · percentiles
After a release that added an owner_user_id filter to the resource listing, p99 rose from 90 ms to 1.9 s while p50 stayed at 40 ms. Traffic and row counts are unchanged. resource carries the index (tenant_id, status, updated_at DESC, resource_id DESC). The new query filters tenant_id and owner_user_id, orders by updated_at DESC, resource_id DESC, and takes 20 rows. On PostgreSQL, explain the shape of the regression, prove it from a query plan, and give the index you would add.
Approach
- Start from the shape. A flat p50 with a moved p99 means a subset of requests changed cost, not all of them, so the first job is naming the subset. Bucket the endpoint's latency by the tenant's row count; the natural hypothesis is that large tenants are a small share of requests and all of the tail.
- Get the plan for the new query on a large tenant with EXPLAIN (ANALYZE, BUFFERS). Expect an index scan over the tenant's range, a filter discarding most of it, then a Sort feeding the Limit, possibly reporting Sort Method: external merge Disk. Read actual rows on the scan node, not estimated.
- Explain why the existing index cannot serve it. A composite B-tree is seekable only as a left prefix, and with no equality predicate on status the scan cannot treat updated_at as an ordering, because rows in the tenant's range are ordered by status first. Everything matching must be read and sorted before LIMIT 20 can apply, so a tenant with 400,000 rows pays 400,000 rows to return 20.
- Add (tenant_id, owner_user_id, updated_at DESC, resource_id DESC). Equality on the first two columns leaves the index ordered by updated_at within that pair, so the plan becomes an index scan that stops after 20 rows with no Sort node. PostgreSQL can scan a B-tree backwards, so the DESC markers matter only if the two sort columns ever disagree in direction; keeping them explicit documents the order the keyset cursor depends on.
Follow-up
- The endpoint paginates with OFFSET. What does page 500 cost with your index, and what does the keyset version cost?
- How would you have caught this before release, given that a 10,000-row seed database produces the same plan shape at an unnoticeable cost?
Suggested practice checklist
Each day targets a group of reported questions and a stack topic from this loop, moving from coding fundamentals to design, then behavioral stories. Every day ends with something you can inspect: code, a sketch or a written outline.
Day 1 — Map the loop and fix your language
- Write the three reported stages (recruiter screening, technical assessments, software design interview) and note what each asks of you.
- Choose one of Java, C# or Python for every coding session this week, and set up a plain editor with no autocomplete to mimic an online test.
- Sort the reported questions into coding, design and behavioral piles and mark your two weakest.
Deliverable: A one-page loop map, the chosen language, and the question list sorted into three piles with the two weakest marked.
Day 2 — Arrays and hash maps, out loud
- Solve three medium array or hash map problems, each time stating the brute-force approach and its complexity before the improved one.
- Name the pattern used in each (frequency map, seen-set, two pointers, prefix sums) and the edge cases: empty input, duplicates, negative values, one element.
Deliverable: Three solved problems, each with a note giving brute-force cost, final cost and the pattern used.
Day 3 — Duplicate accounts and record batches
- Write the duplicate-accounts function: define a duplicate, build a normalised key (for example lower-cased name plus date of birth), group in a hash map and return the groups of two or more.
- Write the batch validator: parse each record, collect all errors with record numbers, write valid and rejected records separately, and keep amounts as integers or decimals.
- Test both with empty input, a single record, an all-invalid batch and a stream too large for memory.
Deliverable: Two working functions in your chosen language and a small test file covering the four edge cases.
Day 4 — Search at scale and class design
- Write a one-page answer on speeding up search over a large database: read the query plan first, then choose indexes (B-tree for equality and range, composite column order), keyset pagination, caching, and when an inverted index is warranted.
- Take one scenario, for example payment methods with several variants, and write the interfaces and classes so a new variant needs no edits to existing code.
- Mark where dependencies are injected so each class can be unit-tested.
Deliverable: A one-page search optimisation answer and a class and interface sketch with the test seams marked.
Day 5 — Concurrency and consistency
- Sketch the design for high-concurrency banking transactions: idempotency key, per-account locking or versioning, outbox, queue to downstream services, and the questions you would ask about load first.
- Say your answer to the distributed-system data consistency question aloud, using the terms saga, outbox and idempotent consumer.
Deliverable: A one-page transaction design diagram with failure cases, plus a spoken answer on distributed consistency you have recorded and replayed.
Day 6 — Architecture, API security and infrastructure as code
- Write a table comparing microservices and a monolith for a bank: deployment, data ownership, transactions, failure isolation, team size and audit trail, and end it with your recommendation.
- Write an ordered security checklist for a public-facing API with one attack for each item.
- Outline how infrastructure as code would work for a cloud deployment: version control, review, separate environments, state handling and a CI/CD pipeline.
- Walk a single request across gateway, authentication, services, database and events, and say where 10,000 requests per second would first strain.
Deliverable: A comparison table, an ordered API security checklist, an infrastructure as code outline and a request-flow diagram.
Day 7 — Behavioral stories and ambiguity
- Write four Situation, Task, Action, Result outlines for the conflict, technical debt, code quality and security, and non-technical explanation questions, using 'I' for your actions.
- Outline how you handle ambiguous or changing requirements: questions you ask, what you write down, how you confirm scope, how you deliver in slices.
- Rehearse every story aloud, then list three questions to ask at the end of an interview about the team's challenges and stack.
Deliverable: Four STAR outlines, one written approach to ambiguous requirements and a list of three closing questions.
Behavioral preparation for Anz
Candidates report that behavioral questions cover conflict resolution, adaptability to changing requirements and deadlines, and communication with non-technical partners. Candidates are advised to use the Situation, Task, Action, Result structure. Prepare four or five stories from real engineering work in which you made the decision, and attach a result to each.
- How do you approach a situation where requirements are ambiguous or constantly changing?
- Tell me about a time you had to pivot your technical strategy based on feedback.
How do you handle data consistency and integrity in a distributed system?
Medium · Technical and Domain Knowledge
Approach
- Start by sorting the data by how much staleness it tolerates. A ledger entry or account balance needs strong consistency inside one service's database, using a single ACID transaction. A notification, a search index or a reporting view can lag and catch up. Saying which is which is the core of the answer.
- Across services, explain why you avoid distributed two-phase commit on the hot path (blocking, coordinator failure) and prefer a saga with compensating steps, or a transactional outbox: write the state change and an event row in the same local transaction, and let a relay publish it.
- Make every consumer idempotent. Carry an idempotency key or event id and record processed ids under a unique constraint, so at-least-once delivery and retries cannot apply a debit twice. Use a version column (compare-and-set) or SELECT ... FOR UPDATE so two writers cannot overwrite each other.
- Protect integrity with rules the database enforces (foreign keys, unique and check constraints, double-entry rows that sum to zero) and add a reconciliation job that compares systems and raises mismatches for a person to resolve rather than silently repairing them.
- Anchor it in your own work: name one system where you chose the guarantee, what failed or nearly failed, and how you detected it. A purely theoretical answer misses what the question asks.
Follow-up
- What happens if the same event is delivered twice, or two events arrive out of order?
- Why not use two-phase commit here, and when would you accept it?
- How would you find out that two services disagree about an account balance?
Tell me about a time you had to resolve a conflict within your engineering team.
Medium · Behavioral and Leadership
Approach
- Pick a real technical disagreement, such as two designs, a review dispute or a release decision, in which you were a participant and made a choice. A story about two other people you watched argue shows nothing about you.
- Describe the situation in two or three sentences, then spend the time on your actions: you asked for the other person's reasoning and restated it, agreed the criteria for deciding (latency, risk, delivery date, operational cost), and gathered evidence such as a benchmark, a prototype or an incident record.
- Show how it ended: a decision, who made it, how you handled being right or wrong, and the effect on the working relationship afterwards. Include escalation only if you tried to resolve it directly first.
- Avoid the stories that sink this answer: casting the other person as the problem, a conflict with no outcome, or one that was only about personality. Keep the other engineer's position fairly stated.
Follow-up
- What if the other person had been more senior than you and kept their view?
- Was there a point where you disagreed with the decision but went ahead with it? How did you handle that?
- What would you do differently if the same conflict started again?
Describe a project where you had to balance technical debt with the need to deliver features quickly.
Medium · Behavioral and Leadership
Approach
- Choose a project where the trade-off was concrete: a particular shortcut (a duplicated module, a missing test layer, a hard-coded integration) and a particular feature deadline. State what the debt was costing, using something measured such as slower changes, repeat incidents or manual work.
- Explain how you decided what to ship and what to defer. Strong choices include shipping the feature behind a flag, limiting scope, taking the shortcut at the edge of the system rather than in the core, and recording the debt as an owned ticket with a trigger.
- Show how you brought product managers into the decision by putting the cost in their terms (delay risk, incident risk, effort), instead of presenting it as an engineering preference. In a bank, mention the review, documentation and test evidence you kept so the shortcut was visible and could be audited.
- Finish with the result and the repayment: when and how the debt was paid, in small steps alongside feature work, and what you would do differently. Avoid the two weak versions: always paying everything down, or never going back to it.
Follow-up
- How did you decide this debt was worth taking rather than postponing the feature?
- What happened to the debt afterwards, and who owned it?
- How would your decision change if the code touched payments or customer data?
How do you ensure your code meets high quality and security standards?
Medium · Behavioral and Leadership
Approach
- Describe your habits as a sequence you can name: a design check before coding, small pull requests with reviewers, unit and integration tests, static analysis and linting in CI, dependency and secret scanning, and a pipeline that blocks a merge on failure.
- Cover security in concrete practice: parameterised queries, input validation, authentication and object-level authorisation checks, least-privilege credentials, secrets kept out of code, and logs that omit personal and card data.
- Tell one story that proves the habit works: a review comment, a test or a scan that caught a real problem such as an injection risk, a missing permission check or an unsafe dependency, and what you changed afterwards. Without one, the answer reads as a list of tools.
- Say how you handle pressure: when a deadline collides with a failing check, you fix it, reduce scope or get a recorded exception with an owner, and you do not silently skip the check. Connect it to the documentation and audit-ready emphasis candidates describe at a bank.
Follow-up
- What would you do if a security scan failed the night before a release?
- How do you decide how much test coverage is enough for a payment flow?
- Tell me about a vulnerability or serious bug that reached production and how you handled it.
Give an example of a time you had to explain a complex technical concept to a non-technical stakeholder.
Medium · Behavioral and Leadership
Approach
- Pick a concept that really mattered to a decision, such as why a retry created a duplicate charge, why a migration needed a freeze, or what eventual consistency means for a balance display. Name the stakeholder and what they needed to decide.
- Lead with the decision and its consequence before the mechanism, then use one analogy that maps accurately and say where it stops being true. Skip jargon, or define each term in a sentence when you use it.
- Check understanding by asking them to say it back or to tell you what they would do next, and follow up in writing with a short summary and options. Show that you adjusted when your first explanation did not land.
- End with the result: the stakeholder made a decision, accepted a trade-off or changed a plan. Avoid the weak stories: talking down to the person, or one where only you spoke and nothing changed afterwards.
Follow-up
- How did you know they understood, and what did you change when they did not?
- What did you do when the stakeholder pushed back on a deadline or a technical constraint?
- Which analogy have you used that turned out to be misleading?