As a Software Engineer at Michigan State University, you play a vital role in building, maintaining, and scaling the technical infrastructure that supports academic excellence, research initiatives, and administrative operations. Your work directly impacts faculty, students, researchers, and staff who rely on stable, secure, and intuitive digital tools. Whether you are developing specialized applications, managing clinical integrations, or designing platforms for campus life, your contributions help drive the institution's mission forward.
This role requires a blend of technical capability, collaborative spirit, and a deep appreciation for the unique ecosystem of a major public research university. You will frequently partner with academic departments, administrative units, and IT teams to translate complex requirements into robust software solutions. The environment balances the structured professionalism of higher education with intellectually stimulating challenges, giving you the opportunity to work on diverse projects that span web development, application support, and database management.
Candidates can expect a collaborative and mission-driven culture where institutional impact often takes precedence over rapid, profit-driven feature releases. Success here depends on your ability to communicate effectively with both technical and non-technical stakeholders, troubleshoot complex systems calmly, and adapt to the varied needs of a large campus community. Expect an environment that values continuous learning and offers stability, making it an inspiring destination for engineers who want their code to support education and public service.
Screening Interview
reportedYou cannot drill a format you do not know, so put the preparation into material that travels. Three pieces of your own work, each rehearsed until you can take a follow-up you did not anticipate, will carry a conversation or a code walkthrough equally well. Specificity is what separates that from filler. A number needs its definition before it means anything: a p99 is over some window and measured at some hop, and a server-side figure excludes the queueing and network time a client would see. The number you cannot qualify is the one to leave out.
What to demonstrate
- Whether your examples carry detail only someone who did the work would hold, such as what the binding constraint actually was, which alternative you rejected and why it was worse, and what you measured on each side of the change
- Whether a number survives one follow-up, meaning you can say what it was measured over and whether it moved because of your change or merely alongside it
- Whether a failure is described with the specific change that followed it, rather than a lesson stated in general terms
- Whether your part in a team effort is stated accurately, including what other people did
How to prepare
- Write a page on each of three projects covering the constraint, the option you rejected, the measurement before and after, and what went wrong. Cut any line you cannot take a follow-up on, since you are writing the parts you will be pressed on rather than a summary.
- Recover the real figures while you still have access: request volume, data size, latency with its percentile and window, team size, timeline. Note where each came from, whether a dashboard, a design document or memory, and mark the estimates so you can say which they are out loud.
- Take your weakest project story to someone who works in a different area and have them ask why four times in succession. The point where you run out of answer is the part to go and re-read before the round.
Technical Interviews
reportedMost of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.
What to demonstrate
- Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
- Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
- Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
- Whether a failing case is isolated and explained before any edit is made to the code
How to prepare
- From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
- Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
- Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.
Behavioral Interviews
reportedWhat you say here is written down by each interviewer and compared afterwards, so the unit of evaluation is a claim someone else could check, not a well-told narrative. Two things make a story checkable: detail only a participant would hold, and a clean line around which part was yours. Vague ownership is the usual failure and it is usually accidental, because engineers say we about the team's work and we about their own, so the thing they personally built disappears into the plural. Name the part you wrote, and name who did the rest.
What to demonstrate
- Whether your details are ones a participant would hold and an observer would not: the constraint that ruled out the obvious approach, the first attempt that failed, the person who objected and on what grounds
- Whether ownership survives a direct question, since a follow-up to we decided is routinely who decided, and an answer that stays plural at that point is read as the work belonging to someone else
- Whether the numbers you quote are ones you would say identically to a former colleague with the dashboard open
How to prepare
- Go through each story replacing every we with either I or a named role (the on-call engineer, the reviewer, the other team) and check the story still holds together. Wherever it stops making sense you have found a part you cannot actually speak to
- Open the artefacts for two of your stories, the pull request, the design doc, the incident notes, and read them for dates and figures you have been rounding in the retelling. Correct your version to match
- For each story write the single sentence you would least want repeated to a former teammate, then either make it accurate or take it out
PracHub editorial advice for the preparation topics above.
Autoscaling on average utilisation against a bell-shaped step function.
Scale-up latency (scheduling, image pull, process and JIT warm-up, connection pool and cache fill) is typically tens of seconds to minutes, while the demand step is under a minute and repeats on a published schedule. By the time the metric crosses a threshold, the period has started and the queue has already built. The workable answer is a schedule-driven pre-warm derived from the tenant calendar plus a load shedder that protects writes, not a more aggressive threshold.
Applying a bulk roster snapshot as authoritative, including its absences.
A truncated file, a partially completed export, or a change in the upstream identifier scheme all present as a large set of rows that vanished, and nothing in the file distinguishes that from a real mass withdrawal. Applied naively it deprovisions enrollments in a single run, and the recovery is not just an undo because in-progress work and derived caches have already moved. Every pipeline that survives has a proportional gate, a recorded diff, and a restartable apply keyed on the snapshot digest.
Hardcoding to the sample inputs
Solve the stated problem rather than the two examples; special-casing a literal to make a sample pass is obvious immediately and reads as either a misunderstanding or an attempt to fake progress. If you genuinely cannot generalise yet, say which part is a stub and what would replace it.
Writing code before the input contract is pinned down
Before the first line, state the types, the size bounds, whether duplicates, negatives or an empty input are possible, whether the input is sorted, whether you may mutate it, and what the function returns when nothing matches. Every one of those answers changes the code, and discovering one at minute twenty costs a rewrite you no longer have time for.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Find peak concurrent attempts to size a pre-warm
You have one district's attempt rows for a single school day: started_at, and an end instant taken as submitted_at when present and server_deadline_at otherwise. There are up to 50,000,000 rows, all inside one local calendar day, and the district's org.time_zone is known. Compute the maximum number of simultaneously in-progress attempts and the second at which it occurs, so capacity can be pre-warmed against the bell schedule. A sort-based sweep is acceptable but is not the best answer here. State your bounds and what happens on a day with a daylight-saving transition.
Approach
- Note the structural fact that decides the algorithm: the output domain is one day at one-second resolution, so there are on the order of 86,400 distinct answer positions while there are 50,000,000 inputs. That inverts the usual sweep, because the coordinate space is far smaller than the data.
- Allocate a difference array over seconds from local midnight, with one sentinel slot past the end so the decrement for an attempt ending in the final bucket has somewhere to land. For each attempt add 1 at the bucket containing
started_atand subtract 1 at the bucket after the one containing the end instant, then prefix-sum once and take the argmax. That is O(N + T) time and O(T) space, which is 86,401 int32 values, about 346 KB — small enough to stay in L2 and to run per section if you want. - Say why this beats the sweep-line answer here. Sorting 2N endpoints is O(N log N) with 100,000,000 entries to materialise; the difference array touches each input twice and never sorts. The sweep only wins when the coordinate space is large or unbounded, which is exactly the condition this problem does not have.
- State the bias the bucketing introduces rather than hiding it. Every attempt is counted in full in every second it touches at all, so the bucketed maximum is greater than or equal to the true instantaneous maximum, never less, which is the safe direction for pre-warming. The overcount is not confined to sub-second attempts: two hour-long attempts that merely share a boundary second without ever overlapping, one ending at 10:00:05.2 and the next starting at 10:00:05.8, already put 2 in that bucket against a true instantaneous maximum of 1. The bound that does hold is per bucket. The attempts touching second s split into those that span it entirely, which by definition coexist at every instant of s, and those with an endpoint inside it, so the overcount at s is at most the number of attempts that begin or end within that second. Equality with an exact sweep is guaranteed when no attempt begins or ends inside the peak second — which holds, for instance, on a fixture whose every endpoint lands exactly on a second boundary.
- Handle the calendar honestly, and fix the indexing convention before sizing anything. Index buckets by elapsed seconds from the UTC instant of local midnight and size the array from the real UTC span between successive local midnights: in a zone that shifts by an hour that span is 82,800 seconds on the spring-forward day and 90,000 on the fall-back day, not 86,400. A hardcoded 86,400 therefore overruns on the fall-back day, whose last hour indexes up to 89,999, and merely leaves 3,600 dead slots at the tail on the spring-forward day, which is harmless. The opposite convention fails the opposite way: bucketing by local wall-clock seconds-since-midnight always stays in range, but maps the fall-back day's repeated hour onto buckets that already hold the first pass through it, folding two real hours of load into one and understating the peak.
- For per-section peaks, do not allocate T buckets per section — 10,000 sections is 3.5 GB. Partition the input by
section_idand reuse one array, or keep only sections whose total attempt count clears a threshold, since the rest cannot produce a peak worth pre-warming for.
Worked solution 25 min
- Compute the UTC instants of this local day's start and the next day's start, subtract to get the true bucket count, and allocate that many plus one sentinel slot.
- Fill the difference array in one pass, using a half-open convention and writing the decrement at end_bucket + 1 so an attempt is counted in the second it ends and the final bucket's decrement lands in the sentinel.
- Prefix-sum in place and track the running maximum and its index, converting that index back to a local wall-clock time for the report.
- Check against a brute-force O(N * T) reference — for each bucket, the number of attempts touching it — on a 500-row fixture spanning two bell periods, including one attempt that starts and ends inside a single second and one pair that abuts inside a second without overlapping.
- Re-run the fixture shifted onto a spring-forward date and onto a fall-back date, confirming the allocation follows the real UTC span each time — 82,800 and 90,000 buckets — and that the fall-back day's final hour, at indices 86,400 and above, lands inside the array rather than past its end.
Follow-up
- Now compute the peak across three districts in different time zones on one shared cluster. What is the coordinate space and does your answer survive?
- Some attempts have neither
submitted_atnorserver_deadline_atbecause they were abandoned. What end do you pick, and how does the choice bias the number you hand to capacity planning? - You need the top ten peak minutes rather than the single peak. What changes, and what does not?
Backfill frozen lateness without converting a hundred million times
A backfill must set due_at_utc on 1,000,000 assignment rows where it is NULL, from due_at_local plus policy_time_zone, and then set is_late on the 100,000,000 attempt rows beneath them whose is_late is NULL, by comparing submitted_at to that instant. Converting one local time per attempt row is correct and far too slow. Give a plan that is both faster and reproducible when re-run months later, state the speed-up you expect, and define the result for a local due time that is ambiguous or nonexistent across a daylight-saving transition.
Approach
- Name why the naive plan is slow rather than asserting it. A local-to-UTC conversion is a binary search over that zone's transition table plus an object construction, on the order of one to three microseconds in a managed runtime; 100,000,000 of them is a few minutes of pure conversion and, once per-row fetch and allocation are included, hours. Nothing about it is wrong — it is the row count multiplied by a cost that did not have to be paid per row.
- Collapse the cardinality. The conversion depends only on
(policy_time_zone, due_at_local), and due dates cluster hard on end-of-day instants for school days, so the distinct pair count is on the order of 10,000 against 100,000,000 attempts. Convert the distinct pairs once into a small mapping, then resolve each attempt with a hash probe. That is roughly a 10,000-fold reduction in conversions, and the per-row cost drops from a conversion to a lookup. - Do it in two stages so the second one is a join, not a computation: materialise
due_at_utcon the 1,000,000 assignment rows from the mapping, then setis_lateon attempts by joining onassignment_id. The attempt stage then performs no time-zone work at all, which is also what makes it re-runnable without the tz database on the path. - Pin reproducibility explicitly. The local-to-UTC mapping is a function of the zone, the local time, and the time-zone database version, because releases correct historical transitions. Record the tzdata version on the backfill run, or a re-run months later can produce a different instant for the same row and a grade will move with no grade event behind it.
- Define the two degenerate cases instead of letting a library default decide. An ambiguous local time in a repeated hour maps to two instants; a nonexistent one in a skipped hour maps to none. Pick the rule — the later instant when ambiguous, the first instant after the gap when nonexistent — encode it as an explicit flag rather than relying on a fold default, and assert both in tests. An 11:59 PM due time is outside the affected hour for most zones, so this is rare, which is exactly why it will not be noticed when it is wrong.
- Respect the freeze. Update only rows where
is_late IS NULL; a row that already carries the flag was decided at submit and must not be recomputed, or the backfill becomes a retroactive regrade. Then chunk the update by primary-key range with a bounded transaction size, because one 100,000,000-row statement holds locks and generates dead tuples for the whole run and cannot be resumed after a failure.
Follow-up
- A district is moved to a different school with a different time zone mid-term. Which rows, if any, change, and what does the freeze rule say?
- The tzdata version on the backfill host differs from the one in the application image. How would you have found that out before the run rather than after?
late_policyisblockfor some assignments, so a late submission should not exist. What do you write for an attempt that submitted after the due instant under that policy?
Rank the longest-stuck grade publications in bounded memory
You are streaming score_publication rows to build an operator view: tenant_id, attempt_id, target, state, attempt_count, created_at, next_attempt_at, last_error_code. Up to 20,000,000 rows are in states pending and failed_retryable, spread across up to 5,000 tenants, and they arrive as an unsorted export you may not re-read. Return the 50 longest-stuck publications overall, the 5 longest-stuck per tenant, and a stuck count per tenant. Memory must be bounded independently of row count. State your bounds and the field you rank on.
Approach
- Pick the ranking field before the data structure, because this is where the answer is usually lost. Stuck duration is
now - created_at, not anything derived fromnext_attempt_at. Exponential backoff pushesnext_attempt_atfurther out with every failure, so ordering ascending by it surfaces the publications that just failed once and buries the row that has been retrying for six days — the ranking inverts exactly on the rows the operator opened the page for. - For the global top 50 — the 50 smallest
created_at— keep a max-heap of size 50 keyed oncreated_at. Push each row; when the heap exceeds 50, pop the maximum. The heap head is the youngest survivor, so a row older than the head displaces it and everything else is discarded in O(1) after one comparison. That is O(N log K) time and O(K) space, with the log K term only paid on the shrinking fraction of rows that beat the head. - For per-tenant results keep a map from
tenant_idto a size-5 max-heap plus an integer count. Memory is O(T * K') = 5,000 * 5 entries plus 5,000 counters, a few megabytes, and it is bounded by tenant cardinality rather than row count. Say that out loud: if tenants were unbounded this structure is not, and you would need a sketch or a two-pass job. - Do not sort. A full sort is O(N log N) over 20,000,000 rows and must materialise all of them; the heap never holds more than K. Note that a database
ORDER BY ... LIMIT 50reaches for the same bounded heap internally, so the interesting case is precisely the one stated — a stream you cannot re-read and cannot fit. - Carry the diagnostic fields through the heap entry rather than re-joining afterward. An operator needs
attempt_countandlast_error_codenext to the age to tell a receiver that is rate limiting from one that is rejecting the payload, and a second pass to fetch them defeats the single-pass constraint you just paid for. - Exclude
deadrows from these structures and count them separately. They are terminal by definition, so mixing them in lets a permanently dead publication occupy a slot in the top 50 forever while genuinely stuck rows rotate beneath it.
Follow-up
- Two publications share the same
created_atto the microsecond. What is your tiebreak, and why does a deterministic one matter for an operator page that refreshes? - The operator wants to replay the top 50 safely. What must be true of the idempotency key for that to be a no-op when the original attempt actually succeeded?
- Tenant cardinality rises to 5,000,000. Which part of your structure breaks first and what replaces it?
Answer a point-in-time roster question against soft-deleted enrollments
enrollment(enrollment_id, tenant_id, section_id, person_id, role CHECK IN ('learner','instructor','aide','observer'), source, begin_date, end_date NULL, status, created_at, updated_at, deleted_at NULL) carries partial UNIQUE (tenant_id, section_id, person_id, role) WHERE deleted_at IS NULL. A learner withdraws in week 3 and re-enrols in week 6; one person is both aide and observer in the same section. Write the query returning learners enrolled in one section on a given date, name the index that serves it, and say what the deleted rows can and cannot tell you about a past roster.
Approach
- Separate the two time axes before writing anything. begin_date and end_date are valid time — when the enrollment was true in the world. deleted_at is transaction time — when the system stopped asserting the row. An 'as of date D' roster is a valid-time question, so the date predicate does the work and deleted_at only filters what you currently believe.
- Write it as: SELECT p.person_id, p.display_name FROM enrollment e JOIN person p ON p.person_id = e.person_id AND p.tenant_id = e.tenant_id WHERE e.tenant_id = $1 AND e.section_id = $2 AND e.role = 'learner' AND e.deleted_at IS NULL AND e.begin_date <= $3 AND (e.end_date IS NULL OR e.end_date >= $3) ORDER BY p.display_name. The role predicate, not DISTINCT, is what stops the dual-role person appearing twice.
- Notice what the partial unique index forbids: two live rows for the same (tenant, section, person, role) even when their date ranges are disjoint, so a genuine withdraw-and-return cannot be modelled as two live intervals. Either the first row is soft-deleted, which destroys the interval, or the uniqueness moves to EXCLUDE USING gist (tenant_id WITH =, section_id WITH =, person_id WITH =, role WITH =, daterange(begin_date, end_date, '[]') WITH &&) WHERE (deleted_at IS NULL), which needs the btree_gist extension and permits disjoint intervals while still rejecting overlaps.
- Index for the equality prefix: (tenant_id, section_id, role, begin_date) WHERE deleted_at IS NULL. Only the leading equality columns plus the first inequality are scan keys, so end_date belongs in the index for the visibility of the filter, not as a second range key.
- State the limit of a soft delete: it stores one bit and one timestamp, so it answers 'is this row still asserted', not 'what did we believe on D'. Reconstructing a past belief needs an append-only history row per change, or the recorded diff from the ingestion run; if support has to answer why a learner vanished, the run record is the artefact, not deleted_at.
Worked solution 20 min
- Seed one section with a straight-through learner, a withdraw-and-return learner, a person holding aide and observer, and one soft-deleted row.
- Run the query for a date inside the withdrawal gap and for a date after the return.
- Attempt to insert the second live interval for the returning learner and observe the partial unique index reject it; replace it with the exclusion constraint and retry.
- EXPLAIN the query and confirm the partial index on (tenant_id, section_id, role, begin_date) is used with no filter on soft-deleted rows.
Follow-up
- The feed sends two overlapping intervals for the same learner in the same section. Which one wins, and what does the exclusion constraint do to that apply?
- A learner withdrew but has a graded attempt in week 2. What does the gradebook show, and which predicate in your query decides it?
- How do you answer 'which learners did we deprovision last Tuesday' with this schema?
Replace per-retry outbox keys with derived ones on a live table
score_publication holds 400 million rows and takes continuous writes: (publication_id, tenant_id, attempt_id, target, idempotency_key TEXT NOT NULL, score_given, score_timestamp, state, attempt_count, next_attempt_at). The key was minted as a fresh UUID per retry, so the same grade has several rows and duplicate deliveries have already happened. Migrate to a key derived from (attempt_id, score revision) with UNIQUE (tenant_id, target, idempotency_key), online. Give the ordered steps with the lock each takes, how you handle the existing duplicates, and what you do with rows currently in_flight.
Approach
- Add a plain nullable column, derived_key TEXT, with no default: a catalogue-only ALTER held for microseconds. Set lock_timeout to a second or two and retry, because the hazard is the queue rather than the statement — the ALTER waits behind one long reader and every query arriving afterwards stacks behind its pending ACCESS EXCLUSIVE. Resist the elegant version: adding a STORED generated column rewrites all 400 million rows under that same lock on current major versions.
- Deploy the dual write before the backfill so every new publication row populates derived_key from attempt_id and the score revision, while the relay still reads the old column. The backfill then chases a closed id range instead of a moving target, and you can bound it.
- Backfill in batches by publication_id range, on the order of 20,000 rows per statement, committing between batches, recording the high-water mark in its own table so a killed run resumes rather than restarts, and filtering WHERE derived_key IS NULL so a replayed batch is a no-op. Pace on replica lag and dead tuples, not CPU: each UPDATE writes a new row version and WAL volume tracks rows touched.
- Resolve the duplicates as a data decision, not a DELETE. Group by (tenant_id, target, derived_key) having count(*) > 1; within each group keep the row in state='succeeded' if one exists, since that is the delivery whose outcome is known, otherwise the newest 'pending'. Move the rest to state='dead' with last_error_code recording the migration, and keep the rows — support has to be able to explain a grade that posted twice, and a deleted row explains nothing.
- Leave in_flight rows alone and drain them. A row in_flight has an outstanding request whose outcome is unknown, so collapsing it either loses a delivery record or races the relay marking a row you just rewrote. Pause the claim loop or wait out the lease, assert zero in_flight for the affected keys, then finish the group.
- Build the constraint without a blocking scan and expect the first attempt to fail: CREATE UNIQUE INDEX CONCURRENTLY, which aborts if any duplicate remains and leaves an index with indisvalid = false that must be DROP INDEX CONCURRENTLY'd rather than reused. Once it builds clean, ALTER TABLE ... ADD CONSTRAINT ... UNIQUE USING INDEX adopts it under a brief ACCESS EXCLUSIVE without rebuilding — which is a second reason derived_key is a real column, since USING INDEX accepts neither an expression index nor a partial one.
Follow-up
- score_timestamp must strictly increase per line item and learner. Does this migration change what the receiver sees, and what would a stale retry do mid-migration?
- A tenant's external endpoint has been failing for a week and has 40,000 rows queued. What does your duplicate collapse do to that backlog?
- How do you prove afterwards that no grade was delivered twice as a result of the migration itself?
How do you ensure code quality and maintainability when working on lon…
How do you ensure code quality and maintainability when working on long-term institutional applications?
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Can you explain how you would troubleshoot a data integration issue be…
Can you explain how you would troubleshoot a data integration issue between two distinct software platforms?
Approach
- Clarify what is being asked and what a complete answer contains.
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Verify, deduplicate and order inbound roster-change webhooks
An upstream provisioning system pushes enrollment-change events to your endpoint. It signs each request, retries any non-2xx with backoff, and disables the endpoint after repeated failure, so your status code is a control signal rather than logging. Deliver the verification procedure, the dedupe key and its retention, the rule that decides whether an event is older than the state you already hold, and what you return for a valid event you cannot process yet. Assume events arrive out of order, duplicated, and occasionally replayed hours late.
Approach
- Verify before parsing. Compute the HMAC over the exact raw request bytes, with the signed payload including the sender's timestamp, compare in constant time, and reject outside a tolerance of a few minutes so a captured request cannot be replayed indefinitely. Accept several active keys addressed by a key id in the header, otherwise rotation is an outage.
- Make the signature the admission gate for everything downstream, including logs: these bodies carry student identifiers, and an unverified body that reaches your log pipeline is a body an attacker chose to put there.
- Dedupe on the sender's event id with a unique constraint, retained at least as long as the sender's maximum retry horizon. A duplicate returns the same 2xx as the first delivery, cheaply, without re-running any side effect.
- Separate delivery order from event order. Each event must carry a per-subject version or source timestamp and the apply is conditional, UPDATE ... WHERE stored_version < incoming_version, so a late delivery of an older enrollment state changes zero rows instead of resurrecting a removed learner. If the sender provides no version, you cannot order the events and must re-read the subject from the source API rather than trust the payload.
- Answer fast and process asynchronously: verify, dedupe, durably enqueue, return inside the sender's timeout. Anything slower converts their retry into a second copy of work already in flight. Reserve 5xx strictly for 'I could not durably record it', because that is the only case where a resend helps.
- Push back honestly when saturated: 503 or 429 with Retry-After keeps the sender's backoff working, where accepting events you will later drop looks like success to both sides.
Worked solution 25 min
- Write the verifier in order: read raw bytes, extract key id and timestamp, reject outside tolerance, HMAC, constant-time compare, then parse.
- Create the dedupe table with a unique index on (tenant_id, source, event_id) and make a duplicate insert return the original response instead of raising.
- Implement the conditional apply and assert that delivering version 3 then version 2 leaves the subject at version 3 with zero rows updated on the second call.
- Replay a captured request an hour later and confirm it is rejected on the timestamp tolerance, not merely caught by the dedupe table.
- Table the response codes: verified and enqueued 202, duplicate 200, bad signature 401, malformed 400, saturated 503 with Retry-After.
Follow-up
- The sender rotates signing keys with no overlap window. What does your endpoint do, and what do you ask them for?
- An event arrives for a section you have never seen. Is that 2xx or 4xx, and why does the answer depend on whether they retry 4xx?
- Six months later, how do you prove which events you received and which you rejected?
If you encountered a critical system bug during a peak academic period…
If you encountered a critical system bug during a peak academic period, what steps would you take to diagnose and resolve it?
Approach
- Check the instrumentation before believing the symptom.
- Say what evidence would prove you wrong, then go and look for it.
- Establish what changed and when, before forming any theory.
Follow-up
- What would you look at first, and what would it rule out?
- What would you add now so this is faster to diagnose next time?
How do you approach learning a new software tool or framework that you…
How do you approach learning a new software tool or framework that you have never used before?
Approach
- Say what evidence would prove you wrong, then go and look for it.
- Establish what changed and when, before forming any theory.
- Check the instrumentation before believing the symptom.
Follow-up
- What would you look at first, and what would it rule out?
- What would you add now so this is faster to diagnose next time?
When you get stuck on a difficult technical problem, what is your syst…
When you get stuck on a difficult technical problem, what is your systematic approach to finding a solution?
Approach
- Pick a bisection that eliminates candidates whichever way it turns out.
- Separate the trigger from the cause; the deploy is rarely the bug.
- Establish what changed and when, before forming any theory.
Follow-up
- What would you look at first, and what would it rule out?
- What would you add now so this is faster to diagnose next time?
These questions test how you approach roadblocks, analyze unfamiliar e…
These questions test how you approach roadblocks, analyze unfamiliar errors, and maintain system reliability under pressure.
Approach
- Say what evidence would prove you wrong, then go and look for it.
- Check the instrumentation before believing the symptom.
- Pick a bisection that eliminates candidates whichever way it turns out.
Follow-up
- How would you tell a cause from a coincidence here?
- What would you add now so this is faster to diagnose next time?
For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Rebuild the primitives by implementing them
- Implement a dynamic array with doubling growth and an operation counter, then change the growth rule to add a fixed sixteen slots instead, and time both for n of ten thousand, a hundred thousand and a million. The fixed-increment version resizes n/16 times at O(n) each, so its total work is quadratic; doubling is what makes append amortised constant.
- Implement a hash map with separate chaining and a load-factor resize, then insert ten thousand keys engineered to land in one bucket and record what happens to lookup time, so that average-case O(1) becomes a claim with a stated precondition rather than a reflex.
- For dynamic-array append and hash-map insert, write down which cost is amortised rather than worst-case, which single operation pays the whole bill, and what a system with a hard per-operation deadline would have to do instead.
Deliverable: Two working implementations plus a timing table showing the input at which each structure's advertised complexity stops holding.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Arrays under an invariant: two pointers, sliding window, binary search
- Solve longest-subarray-with-sum-at-most-K using a sliding window, then run it on an input containing negative numbers and watch it return the wrong answer: extending the window only moves the sum monotonically when every element is non-negative, and that precondition is the whole reason the technique works.
- Write the binary search that finds the first index satisfying a predicate rather than an exact value, put the loop invariant above the loop in a comment, and verify termination on the two inputs that break careless versions: the empty range, and a range where every element satisfies the predicate.
- Compute the midpoint as lo + (hi - lo) / 2 and write one line on why the obvious (lo + hi) / 2 is a genuine defect in a fixed-width integer type and a non-issue in a language with arbitrary-precision integers.
Deliverable: Three solved problems, each with its invariant written above the loop, plus one recorded input on which the sliding window is provably wrong.
Practice prompt ↗Practice prompt ↗03Sorting, heaps, and the greedy argument that has to be proved
- Solve one top-k problem three ways, by full sort, by a size-k heap, and by quickselect, then write the values of n and k at which each becomes the right choice, along with quickselect's quadratic worst case and why a randomised pivot makes that unlikely rather than impossible.
- Implement bottom-up heapify and count sift-down steps to confirm it does linear work rather than n log n, because most nodes sit near the bottom of the tree and therefore move only a short distance.
- Take interval scheduling by earliest finishing time and write the exchange argument out in full: given any optimal schedule, swapping in the earliest-finishing interval keeps it feasible and no smaller. Then construct the weighted variant where that same greedy fails and name what has to replace it.
Deliverable: A three-way top-k comparison with measured crossover points, one written exchange argument, and one counterexample to a greedy rule that looks almost identical.
Practice prompt ↗Practice prompt ↗04Recursion, memoisation, and the step to a table
- Take one problem with overlapping subproblems, such as edit distance or coin change, instrument the plain recursion with a call counter to show the blow-up, then add memoisation and re-count.
- Convert the memoised version to a bottom-up table and state the two properties you relied on: each subproblem's result depends only on its arguments, and the dependencies form a DAG you can enumerate in order.
- Rewrite one deep recursion with an explicit stack, then find the input length at which the original hits the interpreter's frame limit, which defaults to about a thousand frames in CPython, so you know when the rewrite is required rather than decorative.
Deliverable: One problem in three forms, naive, memoised and tabulated, with call counts for each and the input length at which recursion depth becomes the binding constraint.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Graphs, where most of the work is choosing the traversal
- Implement BFS and DFS over one adjacency list, then answer for each which finds a shortest path in an unweighted graph and which you would use to detect a cycle in a directed graph, including why the in-progress versus finished distinction matters for the second.
- Implement topological sort by in-degree, feed it a graph containing a cycle, and confirm the failure signature is that fewer than V nodes come out rather than an exception, then note that the order it produces is one of several valid ones.
- Run a shortest-path search on a graph with a single negative edge weight and show the wrong answer, then write the precondition Dijkstra actually needs, non-negative weights, because it finalises a node's distance the first time that node is popped, and name the algorithm you would switch to and its own limit.
Deliverable: A small graph library with BFS, DFS and topological sort, plus two inputs that produce documented wrong answers under the wrong algorithm choice.
Practice prompt ↗Practice prompt ↗06One day for everything that is not an algorithm
- Sketch one system only to the depth a coding-heavy loop tends to reach: the endpoints, what the service stores, and the single query pattern that decides the schema. Stop at twenty-five minutes.
- Prepare the project answer for an interviewer who codes, which means rehearsing the two levels they push to: the specific thing you built, and why you chose that approach over the alternative they will name. Open with a number and be ready to say what it excludes.
- Prepare the answer to what you would do differently, choosing a real technical mistake with a specific fix rather than a complaint about process or staffing.
Deliverable: One design sketch at endpoint-and-schema depth, plus a project answer rehearsed to two levels of follow-up.
Practice prompt ↗Practice prompt ↗07Solve out loud, under time
- Do three timed problems at twenty-five minutes each in a plain editor with no autocomplete and no execution until the end, then tally separately the failures that were syntax and the ones that were approach, because those two numbers call for different fixes.
- Narrate one solution from the first sentence, stating the approach and its complexity before writing any code, and rehearse the sentence you will use when you realise mid-solution that the approach is wrong.
- Re-solve from blank the two problems you were slowest on this week and compare the times against the day they first appeared.
Deliverable: A recording of one fully narrated solution and a tally that separates syntax failures from approach failures.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
A slipped date is only a bad story if you sat on it. What matters is what you believed when you gave the number, the signal that told you it was wrong, how many days passed before you said so, and what you cut rather than asking for more time. Scope you defended counts as much as scope you dropped.
Describe your experience working in a team environment where different…
Describe your experience working in a team environment where different members had competing priorities or timelines.
Approach
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
Why are you interested in working in higher education at Michigan Stat…
Why are you interested in working in higher education at Michigan State University specifically?
Approach
- Pick a story where you made the decision, not one where you watched it.
- State the situation in two sentences and spend the rest on the reasoning.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Own a stale retry that overwrote corrected grades
A regrade corrected 380 scores. Minutes later a retry from a publication attempt queued before the regrade was delivered to the external gradebook and overwrote 214 of them with the pre-regrade value. The internal score rows were never wrong. Describe an incident of this shape that you owned: how you detected it, how you bounded it while classes continued, what you could not undo inside a system you do not own, what instructors and learners were told, and the change that made a stale retry unable to win.
Approach
- Open with blast radius in the units a district reads: learners times line items overwritten, of those how many an instructor had already seen, how many a learner disputed. Then separate internal truth from external delivery out loud, because the score rows are correct and the repair is a republish, not a recomputation. Teams that conflate the two start by re-running grading and make the drift worse.
- Name the defect precisely rather than calling it a race. Either the idempotency key varied per retry so the receiver treated a stale payload as a new write, or the ordering token was taken from the clock at send time so the stale payload carried a fresh timestamp and legitimately won. Say which one it was; they have different fixes and only the second survives a correct idempotency key.
- Bound before repairing. Set the per-tenant publication concurrency to zero for the affected tenants so the relay stops claiming rows, let in-flight requests drain rather than killing them, then rebuild the pending set from the current score revision. Stopping the whole fleet's relay is the reflex to resist, since every other district is mid-period and their grades are fine.
- Repair by republishing with a strictly greater score_timestamp per line item and learner. You cannot delete a value from someone else's gradebook, so the only instrument you have is a later write that wins; restating the original timestamp is at best a no-op and at worst rejected. A receiving platform may reject a non-increasing timestamp, which helps, but the sender cannot rely on that being enforced everywhere.
- Close on the durable change: the idempotency key derived from attempt_id plus score revision, the ordering token derived from the revision rather than from a clock, and a reconciliation job comparing the latest succeeded publication per (attempt, target) against the current score revision. The alarm is on that drift count and on publications aging in failed_retryable per tenant, not on relay error rate, which stayed flat throughout.
Follow-up
- Some receiving platforms return a conflict on a stale ordering token and others silently accept it. How does that change what you have to do on your side?
- A district asks which value their gradebook held on a given day. What in your schema can answer that, and what cannot?
- What condition moves a publication to a terminal dead state instead of retrying, and who sees that list?
- 01
Describe your experience working in a team environment where different members had competing priorities or timelines.
- 02
Why are you interested in working in higher education at Michigan State University specifically?
- 03
A regrade corrected 380 scores. Minutes later a retry from a publication attempt queued before the regrade was delivered to the external gradebook and overwrote 214 of them with the pre-regrade value. The internal score rows were never wrong. Describe an incident of this shape that you owned: how you detected it, how you bounded it while classes continued, what you could not undo inside a system you do not own, what instructors and learners were told, and the change that made a stale retry unable to win.
Is this an official Michigan State University interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Michigan State University. Rounds and questions reflect what candidates have reported, not a process Michigan State University has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult is the interview process at Michigan State University?
The overall difficulty is generally rated as average or accessible, focusing heavily on practical competence and cultural alignment rather than grueling algorithmic trick questions. While some technical panels can be thorough, the atmosphere is professional and conversational rather than adversarial.
PracHub interview research ↗How long does the hiring process typically take from application to offer?
Due to the institutional nature of a major university, the timeline can span several weeks. It is common to experience intervals of a few weeks between the initial application, phone screen, panel interview, and final reference checks.
PracHub interview research ↗Is remote work or hybrid flexibility common for this role?
Work arrangements depend heavily on the specific department, college, and operational needs of the team you join. Many technical roles offer a blend of on-campus collaboration and flexibility, but candidates should clarify specific location and hybrid expectations during the initial screening.
PracHub interview research ↗What is the best way to stand out during the panel interview?
Differentiate yourself by demonstrating a genuine passion for the university's mission and showing that you listen carefully to user needs. Candidates who combine solid technical fundamentals with clear, patient communication and a service-oriented mindset perform exceptionally well.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22