cswork / libraryOpen practice workspace

Interview guides · Amherst Restaurant

Amherst Restaurant Software Engineer Interview Guide

Amherst Restaurant Software Engineer interview guide: the three reported rounds, Django and Flask questions, behavioral prep and a 7-day plan.

The 60-second brief

Candidates describe Amherst Restaurant as a technology team that sits where dining services, hospitality and a university meet. Candidates say the software covers culinary operations, point-of-sale systems and the dining applications used by students, faculty and staff. The Software Engineer role, as candidates describe it, builds and maintains Python web applications in Django and Flask, replaces older systems, and writes internal tools for kitchen and dining staff. Depending on the job title, the work can also include infrastructure upkeep, deployment pipelines and service-desk troubleshooting.

This guide is for candidates interviewing for a Software Engineer position at Amherst Restaurant, including postings titled Infrastructure Engineer or EDP Programmer / Service Desk Specialist.

Candidates report three rounds over roughly three to five weeks: a Recruiter Screen, a Technical Assessment and a Behavioral Interview. They describe the tone as conversational, with questions about frameworks and past projects rather than abstract algorithm puzzles.

Focus areas

  • Infrastructure Engineering
  • Research Software Engineering
  • Service Desk / IT Support Engineering

Overview

The reported questions cluster in three groups. The first is framework fundamentals: managing state and routing in Flask, the Django ORM and query optimization, authentication and sessions, unit and integration testing, and structuring a project that must absorb a burst of traffic during a campus event. The second is project vision: a system you built from scratch, a project you disliked, technical debt versus features, gathering requirements from non-technical stakeholders, and maintainability for future engineers or student workers. The third is behavioral fit: strengths and weaknesses, why hospitality plus higher education, and pushing back on a stakeholder.

The listed stack is small, so depth in it pays off. Candidates mention Python, Django and Flask, REST APIs, relational databases and Git as the core, with CI/CD, cloud deployment and basic HTML, CSS and JavaScript as extras. Topic labels attached to this role also include infrastructure engineering, research software engineering and service desk or IT support. For the research software label, prepare one concrete example: a reproducible environment (pinned dependencies, a single setup command) or a small tool that a non-engineer could run without your help. Be ready to talk about application code and about the operational side: how a release reaches a server, and how you would troubleshoot when a user reports something broken.

Because the technical stage is described as a conversation, the usual failure is vagueness, not a missed algorithm. Prepare concrete material you can narrate: a real query you made faster and how you proved it, a real login flow with its cookie settings, a real test suite and what it covered, and an architecture you can sketch from memory with the reasons behind each choice. Candidates also mention reviewing a technical writing sample, so practise critiquing and rewriting a paragraph of documentation.

Candidates report slow communication between rounds in some loops. Treat the gaps as part of the process: send a short, polite follow-up and keep your preparation going so you are not rusty when the next round is scheduled.

The Amherst Restaurant interview process

These are the three stages candidates describe, in the order they report them. The wording of each stage comes from candidate reports, not from an official hiring page.

Recruiter Screen

Candidates report a short intro call that covers your experience, your qualifications and why you want the job. Postings under this employer carry different titles (Infrastructure Engineer, EDP Programmer / Service Desk Specialist), so use the call to learn which kind of job you are being screened for and how the week divides between application code and support work.

What to demonstrate

  • Whether your background matches the basic qualifications for the posting
  • Your stated interest in the role and in working at a university-linked dining operation
  • How clearly you summarise your experience in Python web development and any infrastructure or support work

How to prepare

  • Write a two-minute career summary that names Python, Django or Flask, relational databases and Git, then add any deployment, CI/CD or help-desk experience you have.
  • Prepare a specific answer to why a dining and higher-education setting interests you, using a concrete link to your own experience (a campus job, a shift-work job, an ordering app you used).
  • Ask which title the posting maps to, what the next stage involves, and what the usual gap between rounds is, since candidates report slow replies.

Technical Assessment

Candidates report that this stage combines a brief technical assessment with a practical talk about Django and Flask and about projects you have done. Questions about frameworks, past work and project approach are reported across the process, and nothing ties a particular question to this round, so prepare every framework topic. Treat it as a spoken design-and-experience discussion where each answer needs one real example from your own work.

What to demonstrate

  • Working knowledge of Django and Flask and when each fits
  • Understanding of REST endpoints, relational databases, ORMs and query performance
  • How you explain architecture decisions from projects you built
  • Habits around testing and maintainable code

How to prepare

  • Build a tiny Flask app with a blueprint, a session cookie and one database call, and a small Django app with two related models, so you can describe both from hands-on memory.
  • Reproduce an N+1 query in the Django ORM, fix it with select_related or prefetch_related, and keep the before and after query counts as your optimization story.
  • Outline how you would start a Django project for online ordering: settings per environment, apps, models, migrations, auth, tests, deployment and logging.
  • Write a one-page architecture summary of a project you built from scratch, with the reason for each major choice, and practise a burst-traffic plan for a lunch rush or campus event.

Behavioral Interview

Candidates report talk about how you communicate and how you would work with the people around you. Reported topics include self-awareness about strengths and weaknesses, handling disagreement with stakeholders, explaining a technical issue to a non-technical colleague, and reviewing a technical writing sample or describing how you document code. The behavioral questions candidates report are the ones to rehearse here, with a real story behind each.

What to demonstrate

  • Honest reflection on strengths and areas you are improving
  • Clarity when explaining technical constraints to non-technical people
  • Written communication, including documentation habits
  • Collaboration and openness to feedback in a university-style setting

How to prepare

  • Write story outlines for the disliked project, the stakeholder push-back and the technical explanation to a non-technical colleague, each with the situation, your decision, the result and a lesson.
  • Take a dense paragraph of technical documentation and rewrite it for a reader who has never seen the system, since a writing-sample review is reported.
  • Prepare a sincere reason for choosing hospitality plus higher education, and a concrete answer for how you stay current on frameworks.

Where candidates lose points

Giving a textbook definition of the Django ORM or Flask routing with no example from your own code

Keep one working project for each. For the ORM, rehearse a specific slow view, the query count before and after select_related or prefetch_related, and the index you added. For Flask, know where request state lives (session cookie, database, cache, per-request context).

Treating a conversational technical stage as casual and being unable to recall the reasons behind your own architecture

Write a one-page summary of a project you built from scratch: components, data model, deployment, what you would change. Practise saying why for each choice, since candidates report questions about how you envisioned the architecture.

Preparing only application code when the posting mixes development with infrastructure or service-desk work

Be ready to describe how a release reaches a server, the order in which you would check things when a user says an ordering page is broken, and a time you handled a support-style problem. Ask during the recruiter call which title the job maps to.

Telling the disliked-project or stakeholder push-back story as a complaint about other people

Structure it as situation, your decision, the evidence you used, the result and a changed habit. Credit the stakeholder's underlying need, and keep the focus on what was in your control.

Submitting vague feedback when asked to review a technical writing sample, or going quiet during gaps between rounds

Practise critiquing a paragraph of documentation for audience, order and missing steps, then rewriting it. After a screen or interview, send a short, polite follow-up if you hear nothing, as candidates report delays.

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

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

Diff a projection against the primary without per-row point reads

Hard · reconciliation · range hashing · throttling

The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.

Approach

  1. Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
  2. Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
  3. For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
  4. Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.

Follow-up

  • The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
  • Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?

Merge partitioned event streams into one ordered feed with bounded lateness

Hard · k-way merge · watermarks · out-of-order streams

The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.

Approach

  1. Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
  2. Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
  3. Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
  4. Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.

Follow-up

  • The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
  • The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?

Can you explain the Django ORM and how you would optimize a complex database query?

Medium · Frameworks and Technical Fundamentals

Approach

  1. Explain the model: a QuerySet is lazy and builds SQL as you chain filter, exclude and annotate. It runs only when evaluated (iteration, list(), len(), bool(), or slicing with a step), so I check where evaluation actually happens before optimising.
  2. Measure first: I print str(qs.query) or call qs.explain(), count queries with django-debug-toolbar or assertNumQueries in a test, and look for the N+1 pattern of one query per row inside a loop or template.
  3. Fix N+1: select_related for ForeignKey and OneToOne (a single JOIN), prefetch_related for ManyToMany and reverse ForeignKey (one extra query per relation, matched in Python), and Prefetch(queryset=...) when the related rows need filtering.
  4. Push work to the database: annotate and aggregate with Count or Sum instead of summing in Python, use exists() rather than count() for presence checks, values() or values_list() to skip model instantiation when I only need raw columns, F() expressions for race-free updates, and bulk_create or bulk_update for batches.
  5. Edge cases: only() cuts the selected columns but still builds model instances, and reading a deferred field later runs one extra query per row, a hidden N+1, so I use it only when I know which fields the code touches. Annotating two multi-valued relations in one query inflates aggregates through the joins (Count(..., distinct=True) or a Subquery with OuterRef fixes it). When I drop to .raw(), I pass bind parameters, never string formatting.

Follow-up

  • When would prefetch_related beat select_related on a ForeignKey?
  • How do you add an index to a large Postgres table without blocking writes (AddIndexConcurrently in a non-atomic migration)?
  • How would you paginate a long order history once OFFSET pagination gets slow?

Keep soft-deleted accounts from blocking re-registration

Easy · soft delete · unique constraints · engine differences

app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.

Approach

  1. Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
  2. Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
  3. For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
  4. State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.

Follow-up

  • A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
  • How do you honour an erasure request while resource_revision.actor_user_id still references this table?

How do you manage state and routing in a Flask application?

Medium · Frameworks and Technical Fundamentals

Approach

  1. Routing: I use an application factory (create_app) and split routes into Blueprints registered with a url_prefix, use converters such as int:order_id and explicit methods=["GET", "POST"], and build links with url_for so a renamed path does not break templates.
  2. Request state: per-request data goes on flask.g or the request object, and I read configuration through current_app.config inside blueprints rather than importing the app object, which avoids circular imports.
  3. Session state: the default flask.session is a cookie signed with SECRET_KEY, so the client can read it but not alter it. I keep only small, non-secret values there (user id, CSRF data) and move larger or sensitive state server-side with Flask-Session on Redis or the database.
  4. Shared state: module-level globals live per worker process, so under gunicorn with several workers they diverge. Anything that must be shared, such as counters, carts or caches, goes in Redis or the database, and a Flask-SQLAlchemy session is cleaned up at request teardown.
  5. Edge cases I mention: a route declared with a trailing slash redirects the bare URL to it, while a route declared without one returns 404 for the slashed form. I also mention 405 for an unlisted method and testing routes with app.test_client() against a test config.

Follow-up

  • A cart grows past what a cookie can hold. Where do you move it, and what changes?
  • How would you share a rate-limit counter across several gunicorn workers?
  • How do you test a blueprint in isolation from the rest of the app?

What is your approach to writing unit and integration tests for backend services?

Medium · Frameworks and Technical Fundamentals

Approach

  1. Split by layer: fast unit tests with pytest for pure logic such as pricing, menu availability or discount rules, with no database or network, and fewer integration tests that go through the HTTP layer with Django's test Client (or DRF APIClient) or Flask's app.test_client() against a real database.
  2. Database isolation: Django's TestCase wraps each test in a transaction and rolls it back, while TransactionTestCase truncates tables and is slower. I use factory_boy or pytest fixtures for data, and I test against the same engine as production, because SQLite and Postgres differ on things like case sensitivity and JSON fields.
  3. Mock only at the boundary: an external payment or point-of-sale API gets unittest.mock.patch applied where the name is looked up, or the responses library for HTTP. I do not mock the ORM, and I freeze time with freezegun for anything date-dependent.
  4. Assert behaviour, not just status codes: response body, database side effects, and the failure paths (400 on invalid input, 401/403 without auth, 404, duplicate submissions). Every bug fix gets a regression test that fails before the fix.
  5. Run the suite in CI on every push. I treat coverage as a signal for untested branches rather than a target, and I quarantine and fix flaky tests instead of retrying them until they pass.

Follow-up

  • How do you test code that runs in transaction.on_commit or in a Celery task?
  • The suite now takes too long to run on every push. What do you change first?
  • What is the difference between patching where a function is defined and where it is used?

How would you structure a project that needs to scale up quickly during a campus event?

Medium · Frameworks and Technical Fundamentals

Approach

  1. Size the spike first: expected peak requests per second, how long the peak lasts, the read/write mix (menu views versus orders placed), and which paths must not fail (placing and paying for an order) versus which can degrade (recommendations, history).
  2. Make the app tier stateless so it scales horizontally behind a load balancer: sessions in Redis or the database, not in process memory. I pre-scale before a known event, because autoscaling reacts after load arrives.
  3. Protect the database: cache read-heavy endpoints like the menu in Redis or at a CDN with a short TTL and explicit invalidation, put PgBouncer in front of Postgres because every gunicorn worker holds connections, and add indexes for the order-lookup queries.
  4. Keep the write path short: move receipts, emails and point-of-sale sync to a queue (Celery) and respond quickly. I put an idempotency key on order submission so a client retry cannot create a duplicate order, and I prevent overselling with a conditional update (stock = F('stock') - 1 filtered on stock > 0) or select_for_update.
  5. Prove it before the event: load test with Locust or k6 at above the expected peak, watch p95 latency, error rate and DB connections, and keep feature flags plus rate limits ready to shed non-critical features.

Follow-up

  • Which component fails first under a sudden spike, and how would you know?
  • How do you stop a limited item from being sold more times than it is in stock?
  • Autoscaling is too slow for a spike that starts at a fixed time. What do you do instead?

Describe a recent project you built from scratch. How did you envision its architecture?

Medium · Past Experience and Project Vision

Approach

  1. Open with the problem and constraints in two sentences: who used it, the expected load, team size, and the existing systems it had to talk to. The architecture only makes sense against those constraints.
  2. Walk the components concretely: client, API layer (for example Django or Flask and why), data model and the main tables, auth, background jobs, hosting and the CI/deploy path. Say what each one owns.
  3. Give at least two real trade-offs with the option you rejected, such as a monolith over services because one developer maintained it, or Postgres over a document store because orders need transactions and joins.
  4. Close with evidence and hindsight: what happened after launch (an incident, a measured latency or adoption number you can stand behind) and one decision you would change now and why.
  5. Use "I" for your own decisions and be clear about what others built. The answer that sinks you is a list of technologies with no reasons attached.

Follow-up

  • What breaks first if usage grows tenfold?
  • How did you deploy changes, and how would you roll back a bad release?
  • Which architectural decision would you reverse today?

How do you prioritize technical debt versus building new features?

Medium · Past Experience and Project Vision

Approach

  1. Make debt concrete before ranking it: what it costs today in incidents, hours lost per change, onboarding time or security exposure. Debt with no measurable cost goes to the bottom of the list.
  2. Rank by cost of delay against effort. An unsupported framework or Python version that no longer gets security fixes, missing backups, or no tests on the payment path outrank cosmetic refactors.
  3. Pay debt down alongside features where possible: refactor the module a feature already touches, and keep a standing share of each cycle for the top debt items instead of waiting for a dedicated cleanup project that never gets scheduled.
  4. Explain it in stakeholder terms: "this change makes the next three menu features faster to ship and removes the cause of last month's outage", not "the code is messy".
  5. Back it with one real example: the debt, how you sized it, what you deferred to make room, and the result.

Follow-up

  • Tell me about debt you chose to defer that later caused a problem.
  • How do you convince a non-technical manager to fund a framework upgrade?
  • How would you show that paying down a debt item actually paid off?

Walk me through your process for gathering requirements from non-technical stakeholders.

Medium · Past Experience and Project Vision

Approach

  1. Start from the workflow, not the feature request: shadow or interview the people doing the work (for example staff at a service counter), ask what they do today, where it breaks and how often, and capture real examples.
  2. Identify who decides and who uses it, and list the constraints early: systems it must integrate with, data that already exists, accessibility, and busy periods when nothing can change.
  3. Turn what you hear into short user stories with acceptance criteria in plain language, and confirm them with a wireframe or clickable mockup before writing code, because non-technical stakeholders react to screens far more reliably than to specs.
  4. Separate must-haves from wishes, write the agreed scope down and get explicit sign-off, then ship in small increments and demo each one so misunderstandings surface early.
  5. Edge cases to name: two stakeholders asking for conflicting things (escalate to the decision-maker with the trade-off stated), and requirements changing mid-build (log each change with its cost to the schedule).

Follow-up

  • Two stakeholders want contradictory behaviour from the same screen. What do you do?
  • A requirement changes after half the build is done. How do you handle it?
  • How do you find the requirements nobody mentions because they seem obvious to them?

How do you ensure your code is maintainable for future engineers or student workers?

Medium · Past Experience and Project Vision

Approach

  1. Enforce conventions with tools rather than memory: a formatter (Black or Ruff format), a linter (Ruff or flake8), type hints checked with mypy, pre-commit hooks, and CI that blocks a merge when any of them fail.
  2. Keep the structure predictable: Django apps or Flask blueprints split by domain, thin views, business logic in plain functions that are easy to test, and no clever metaprogramming that a newcomer cannot follow.
  3. Make setup a single documented command (for example docker compose up) with a README that covers running, testing and deploying, plus short decision records explaining why key choices were made.
  4. Treat tests as documentation and as a safety net, so someone new can change code without fear. Keep pull requests small and reviewed, and pin dependencies so the project still builds a year later.
  5. Write comments and docstrings that explain why, not what, and keep runbooks for the operational tasks (deploys, restoring backups) that a rotating contributor will need.

Follow-up

  • How would you get a new student worker productive in their first week?
  • How do you document a legacy system that nobody on the team fully understands?
  • What do you check first when reviewing a newcomer's pull request?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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 seven days follow the reported loop: map the rounds, practise the Flask, Django, auth, testing and scaling questions, then project-vision and behavioral stories, ending with a spoken run-through.

Day 1 — Map the loop and inventory your projects

  • Write the three reported rounds (Recruiter Screen, Technical Assessment, Behavioral Interview) and note the kinds of conversation candidates describe for each.
  • List three real projects: one built from scratch, one you disliked, and one where you pushed back on a request. Write four sentences about each: situation, your decision, result, lesson.
  • Write your two-minute career summary and a reason for choosing a dining plus university setting, then note questions to ask the recruiter about the job title and next stage.

Deliverable: A one-page loop map, a three-project inventory and a two-minute career summary with your questions for the recruiter.

Day 2 — Flask state, routing and framework choice

  • Build a small Flask app with two blueprints, a route using a URL converter, a session cookie and one database-backed endpoint.
  • Write down where each kind of state lives in that app: signed cookie, database row, cache, per-request context.
  • Prepare a spoken comparison of Django and Flask for a small internal tool versus an online ordering application.

Deliverable: A running Flask app plus a spoken answer on state and routing and a Django versus Flask comparison.

Day 3 — Django ORM and query optimization

  • Create two related Django models, reproduce an N+1 query, and fix it with select_related or prefetch_related, recording query counts before and after.
  • Read the EXPLAIN output for one slow query, add an index and note what changed.

Deliverable: A before and after query-count log, an EXPLAIN note, and a worked solution to the soft-delete practice problem.

Day 4 — Authentication, sessions and testing

  • Implement login and logout in Django or Flask with secure cookie flags, CSRF protection and a rotated session id, and write down how you would revoke sessions.
  • Write unit tests for a service function and an integration test that hits an endpoint with a test database, then explain what each layer catches.
  • Rehearse a two-minute answer on authentication and a two-minute answer on your testing approach.

Deliverable: A working login flow with its settings listed, a small test suite, and two spoken answers.

Day 5 — Scaling for a campus event, troubleshooting and performance debugging

  • Sketch how an ordering application survives a lunch rush or campus event: caching, connection pooling, background jobs, several app servers behind a load balancer, health checks and a rollback path.
  • Describe your deployment pipeline end to end, from commit to a running server.
  • Write a troubleshooting sequence for a user reporting that the ordering page is broken: reproduce it and gauge scope (one user, one building, everyone), check recent deploys, read the app and nginx logs and the error tracker, check database connections and slow queries, check DNS, TLS and the network, then roll back or fix and write up the incident.

Deliverable: A one-page scaling and deployment plan, a written troubleshooting sequence, and a diagnosis from the debugging practice problem.

Day 6 — Project vision, technical debt, requirements and maintainability

  • Narrate the architecture of your from-scratch project in five minutes: users, components, data model, deployment, trade-offs, and what you would redo.
  • Write a rule for weighing technical debt against features, a list of questions for gathering requirements from a non-technical stakeholder, and a maintainability checklist (README, tests, naming, small modules, a runbook).
  • Outline how you would modernise a legacy point-of-sale integration: list the current integrations, write characterization tests around the old interface, cut over through an adapter or strangler-fig approach, run old and new in parallel and reconcile orders and totals, keep a rollback plan, and schedule the cutover outside meal rushes.
  • Rewrite one paragraph of technical documentation for a reader new to the system.

Deliverable: An architecture narrative, a debt-versus-features rule, a requirements question list, a maintainability checklist, a legacy point-of-sale modernisation outline and a rewritten documentation paragraph.

Day 7 — Behavioral stories and full spoken run-through

  • Write and rehearse outlines for the strengths and weaknesses, hospitality and higher education, disliked project, stakeholder push-back and staying-current questions.
  • Record yourself answering each aloud, then trim any answer that runs past a story with a clear result.
  • Write a short, polite follow-up email you could send if a round goes quiet, and finish with one full pass through the technical answers from days 2 to 6.

Deliverable: Five recorded behavioral answers, a follow-up email and a final checklist of weak spots.

Behavioral preparation for a campus dining team

Candidates describe the behavioral stage as a conversation about communication, self-awareness and how you work with non-technical stakeholders. For this role, build each story around a decision you made, what it cost and what you changed afterwards, and have one example where you explained a technical issue in plain language.

  1. Tell me about a time you had to explain a complex technical issue to a non-technical colleague.

How do you handle user authentication and session management in your web applications?

Medium · Frameworks and Technical Fundamentals

Approach

  1. Name the mechanism first. In Django, SessionMiddleware keeps session data on the server (database or cache) and sends only a signed session id in a cookie, and django.contrib.auth handles login, logout and password storage. In Flask, the default session is a signed cookie that the client can read but not alter, so keep only a user id there, or use Flask-Login with a server-side store.
  2. Cover the credentials: hash passwords with a slow salted algorithm (Django defaults to PBKDF2; Argon2 or bcrypt are common), never log or email them, and use single-use expiring tokens for password reset.
  3. Cover the cookie and request defences: HttpOnly, Secure and SameSite flags, CSRF protection on state-changing forms (Django middleware, Flask-WTF), HTTPS only, and rotating the session id at login to prevent session fixation (Django's login does this).
  4. Separate authentication from authorization: describe how routes check permissions (Django permissions and groups, or a decorator in Flask) so a logged-in student cannot call a staff-only endpoint.
  5. For an API consumed by a separate front end, compare server sessions with tokens: tokens need an expiry and a revocation plan, server sessions revoke instantly but need shared storage once you run several app servers.

Follow-up

  • How would you force every user out after a password change or suspected compromise?
  • Would you use cookie sessions or tokens for a mobile ordering app, and why?
  • How do you throttle login attempts without locking out legitimate users on a shared campus network?

Tell me about a project you worked on that you did not enjoy. What were the challenges?

Medium · Past Experience and Project Vision

Approach

  1. Pick a real project where the dislike had identifiable causes, such as unclear requirements, a brittle legacy codebase, constant scope changes or tooling you could not influence. State the situation in two sentences, then name the specific challenges.
  2. Spend most of the answer on what you did inside your own control: you wrote down requirements, added tests around the legacy code before changing it, raised the risk early, split the work into smaller releases, or documented the rough edges.
  3. Report the outcome honestly, with whatever you can measure (bugs closed, releases shipped, hours saved), including if it only partly worked.
  4. Close with the lesson as a changed habit, for example confirming scope in writing at kickoff, and say how you would approach the same project today.
  5. Avoid blaming named colleagues, avoid a trivial complaint such as 'the meetings were long', and avoid sounding as though you disengage when work is unglamorous; legacy maintenance and support-style work are part of this role's reported scope.

Follow-up

  • What would you do differently if you were handed that project again today?
  • Did you raise your concerns early, and to whom?
  • How do you stay productive on work that does not interest you?

What are your greatest professional strengths and weaknesses?

Medium · Behavioral and Cultural Fit

Approach

  1. Choose two strengths that matter for a Python web and infrastructure role, for instance tracing a production bug from log to cause, or writing documentation others actually use. For each one give a short story or number as evidence rather than an adjective.
  2. Choose a real weakness that is not disqualifying for the job, such as over-engineering early designs, hesitating to ask for help, or being slow to write tests first on throwaway scripts. A disguised strength such as 'I work too hard' reads as evasive.
  3. For the weakness, state the steps you have taken (a checklist, a pairing habit, a time limit on design) and one piece of evidence that it is improving.
  4. Use a three-part shape for each point: the claim, the evidence, the effect on the team, and keep the whole answer short enough to leave room for follow-ups.
  5. Do not choose a weakness the posting requires you to have (for example, poor written communication), and do not list five of each.

Follow-up

  • What feedback have you received that matches this weakness?
  • How do you know it is getting better?
  • Which of your strengths would matter most in a role that mixes development with support work?

Why are you interested in working at the intersection of hospitality and higher education?

Medium · Behavioral and Cultural Fit

Approach

  1. Answer in two parts: why this domain, and why you in it. Anchor the domain part in something true: you worked in food service, you use a campus dining app, or you like software whose users are in the building next door.
  2. Tie the answer to what candidates report about the work: ordering applications, point-of-sale systems, internal tools for kitchen and dining staff, and a university community as the user base. Say what makes that kind of software interesting to you, such as a hard peak-hour load or users who are not technical.
  3. Connect your own experience to it. A past role supporting non-technical users, a project with real deadlines, or time in an academic setting each shows fit without needing to praise the employer.
  4. Mention the role mix honestly: application development, with infrastructure or service-desk responsibilities depending on title, and say why that appeals to you or how you would handle it.
  5. Do not flatter, and do not say you want any coding job. If you have no direct domain background, say so and describe what you learned while preparing for the interview.

Follow-up

  • What do you think is hardest about software for a dining operation during a lunch rush?
  • Have you worked with non-technical colleagues or users before? Give an example.
  • What would you want to learn in your first few months in this environment?

Tell me about a time you had to push back on a stakeholder's request. How did you handle it?

Medium · Behavioral and Cultural Fit

Approach

  1. Choose a request where pushing back was the right call and where you stayed on good terms. Example: a stakeholder wanted a change to an ordering flow just before a busy period.
  2. Start by showing you understood the need behind the request: ask what problem they are solving and what happens if it is not done.
  3. Explain the cost in terms they can weigh: time, risk to existing features, maintenance, or conflict with an earlier commitment. Present alternatives such as a smaller version now and the full version later, or a manual workaround for this week.
  4. Say how you communicated it: a short written summary of options and a recommendation, so the decision is visible and can be revisited.
  5. Report the outcome, including if the stakeholder decided differently and you complied. A strong story shows judgement and relationship care; a weak one casts the stakeholder as foolish or the pushback as a refusal.

Follow-up

  • What would you have done if the stakeholder insisted on the original request?
  • How did you keep the relationship healthy afterwards?
  • When have you decided not to push back, and why?

How do you stay updated with new technologies and frameworks?

Medium · Behavioral and Cultural Fit

Approach

  1. Describe a concrete routine rather than 'I read blogs': release notes for the Python version and for Django or Flask, the deprecation warnings your test suite prints, the documentation of a library you are about to adopt, and one small side project per new tool.
  2. Give one example where learning changed a real decision, such as moving off a deprecated Django feature before an upgrade, or adopting a type checker after reading about it and trying it on one module.
  3. Show judgement about adoption: you try a tool on something low-risk, compare it with what the team already has, and consider maintenance cost and who else must learn it before you recommend it.
  4. Mention how you share what you learn, such as a short write-up or demo for teammates; candidates report an emphasis on continuous learning and mentoring, so this is a natural fit.
  5. Avoid listing fashionable technologies you have not used, and avoid implying you chase every new framework.

Follow-up

  • What is the most recent thing you learned and actually used at work?
  • Tell me about a technology you decided not to adopt and why.
  • How do you evaluate a new library before adding it to a project?