As a Software Engineer at Epic, you will build and maintain software that touches the medical records of over 305 million patients worldwide. Epic creates integrated healthcare software used by major hospital systems, academic medical centers, community clinics, and patients across the globe. In this role, your code directly impacts patient care, clinical workflows, and critical healthcare infrastructure where system reliability, real-time data access, and data privacy are paramount.
Engineers at Epic work on large-scale systems handling massive transaction volumes, complex medical data models, and mission-critical uptime requirements. You will work across the full stack—from low-level database engine performance and data integration protocols to intuitive clinical user interfaces used daily by doctors, nurses, and administrative staff. You will solve complex algorithmic and architectural problems, such as scaling real-time patient charts, optimizing health record queries, and integrating interoperability standards across healthcare networks.
The environment at is fast-paced, highly collaborative, and deeply impact-driven. The engineering culture places high value on analytical thinking, rapid domain learning, and clear communication. Success in this role requires strong core computer science fundamentals, a willingness to master proprietary and specialized technical stacks, and a practical mindset focused on delivering robust, high-quality healthcare applications.
Phone Screen
reportedBefore anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Skills Assessment
reportedThe same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.
What to demonstrate
- In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
- In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
- Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes
How to prepare
- Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
- Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
- For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
Final Interview
reportedNobody in the room with you decides this. Interviewers typically write their rounds up separately, often before seeing anyone else's, and the outcome is settled later from those write-ups. A split panel gets resolved by whichever note carries specific evidence, so what you want out of each room is one concrete thing that person could write down: a bug you caught yourself, a trade-off you named, a decision you owned. The rest is arithmetic. The project you describe in a behavioural conversation is often the same system you sketched an hour earlier, and the two accounts have to agree.
What to demonstrate
- Whether the scale, team size and timeline you attach to a project hold steady when that project resurfaces in a different round
- Whether each interviewer leaves with a specific thing to cite rather than a general impression of competence
- Whether a trade-off you defended in one round survives a challenge in another, instead of being quietly swapped for the answer the new interviewer seemed to want
- Whether a question you have already answered earlier in the day gets the same answer at the same depth, without visible impatience
How to prepare
- Write a one-page sheet per project fixing the figures you will quote — request volume, data size, team size, elapsed time, what broke — and say them aloud from the sheet until they come out identical every time
- For each round on the schedule, decide in advance the one sentence you want in that person's notes, then check in a mock that you said it outright instead of leaving it to be inferred
- Have someone ask you the same project question twice, an hour apart, and diff the two answers for numbers that moved or a trade-off that reversed
Technical Evaluations
reportedInput bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.
What to demonstrate
- Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
- Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
- Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
- Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply
How to prepare
- For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
- For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
- Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
5 candidate reports. Individual accounts describe a particular role and hiring cycle.
Epic Software Engineer onsite: evolving patient-database design and backtracking
I started the day with an online video overview that lasted about forty-five minutes. It covered what Epic does and the software it provides. After that, I moved into one-on-one conversations with engineers. One was a lighter discussion where I could ask questions and hear about their experiences. The next engineer conversation was more technical and felt like a system design prompt that kept bui…
Read full experienceEpic Software Engineer, difficult assessment led to an offer
I started with a quick phone interview with an existing employee. It was mainly a fit check, and then I moved directly into scheduling an assessment. The assessment was genuinely hard, longer and more challenging than I expected, and it was the part that required the most effort. After finishing it, I made it far enough to receive an offer. I ultimately declined because a better opportunity came…
Read full experienceEpic Software Engineer interview with HR call and coding assessment
My process started with a phone call with HR. It had a resume and behavioral focus, with some discussion of my background and questions about how I think and present myself. After that, I completed an assessment test. That was clearly the hardest part. It wasn't described as a single coding-only challenge, but the coding section pushed me the most. The test had to be completed before or after the…
Read full experienceEpic Software Engineer interview: assessment funnel with math, logic, and coding
I started with a basic phone interview about my resume and why I was interested in the role and company. After that, I completed an assessment with several parts: a personality section, math and logic sections, and coding. Once I finished those, I had a longer final interview that combined a case study with behavioral questions. The final conversations felt fairly normal. They focused mostly on f…
Read full experienceEpic Software Engineer Interview Experience — MIIS, Logic Puzzles, and Four Coding Tasks
Online Assessment Format Two minutes: Answer as many short logic/reasoning questions as possible. Twenty MIIS questions. Fifteen math/logic questions. Four programming questions. MIIS Questions MIIS arithmetic Calculate the value of 1 + 1 * 3 / 2 + 7. MIIS strings and type conversion Given A = "JOHN", B = "JANE", and C = "3DOES", find the value of 0.A + B + C - 3. Math / Logic Questions Coins Som…
Read full experiencePracHub editorial advice for the preparation topics above.
Treating a medical record number or a member ID as a globally unique key and joining on it directly.
These identifiers are unique only within the authority that issued them. Two facilities in one network routinely have the same medical record number for different people, and member IDs get reissued when someone changes plans. Joining on the bare value merges two patients' records, which is the most damaging failure available in this domain, and it passes every test written against a single-facility fixture because the collision only appears once a second source is connected.
Upserting on the order or result identifier, so a correction overwrites the original row.
It makes the question 'what did the clinician see at 14:02' unanswerable, which is exactly what an incident review or a legal hold asks. It also leaves downstream consumers that already acted on the preliminary value with no correction event to react to, because the state transition was collapsed into a single mutated row and never emitted.
Treating a network call as though it were a local function call
A remote call can be slow, fail, or return after you stopped waiting, so name the timeout, the retry policy, and what the caller sees while the dependency is down. A call with no timeout turns one slow dependency into an exhausted thread or connection pool in every service upstream of it.
Abandoning working code to chase the optimal solution
Get the straightforward version correct, state its complexity, and only then optimise, keeping the working version until the faster one passes the same cases. A correct quadratic solution with a stated path to linear beats a half-written optimal one that never ran.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
"Explain how you would write a function to evaluate mathematical expre…
"Explain how you would write a function to evaluate mathematical expressions or custom language tokens."
Approach
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
"Write a function to generate all valid permutations or recursive comb…
"Write a function to generate all valid permutations or recursive combinations given a set of input constraints."
Approach
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
"Solve a backtracking problem to find all paths meeting specific crite…
"Solve a backtracking problem to find all paths meeting specific criteria without relying on an external compiler."
Approach
- Name the brute-force solution and its complexity before improving on it.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- How does this change if the input no longer fits in memory?
- What is the worst case, and how likely is it on real data?
Reconstruct what the chart displayed at five million past instants
observation_result holds 200 million versions: observation_id, enterprise_person_id, filler_order_id, loinc_code, value_numeric, result_status, collected_ts, issued_ts, version, supersedes_observation_id. An incident review hands you 5 million queries of (enterprise_person_id, filler_order_id, loinc_code, as_of_instant). For each, return the version that was visible at that instant, meaning the one with the greatest issued_ts at or before it. Neither side fits in memory. The per-query scan is correct. Explain precisely why it is too slow, then give a plan with its complexity.
Approach
- Name the clock before naming an algorithm. Visibility is issued_ts, the release time. collected_ts is when the specimen was drawn and can precede release by hours, so ordering on it reports a correction as visible long before anyone could have seen it. No amount of index work rescues the wrong column.
- Be exact about why the naive plan fails, because the interviewer is testing whether you can tell arithmetic cost from I/O cost. Per query the chain scan is O(V_k) and the arithmetic is trivial, but 5 million independent lookups into a 200-million-row structure that does not fit in RAM is 5 million random reads. The job is bounded by seeks per query, not by comparisons, and buying a faster comparison changes nothing.
- Convert random access into sequential access. Hash-partition both sides on the chain key (enterprise_person_id, filler_order_id, loinc_code) into P shards sized to fit memory, sort each shard's versions by (chain key, issued_ts) and its queries by (chain key, as_of_instant), and sweep the pair in lockstep. Total O((V + Q) log(V + Q)) with external sort, replacing Q seeks with two sequential passes.
- Inside a chain the sweep is linear, not logarithmic, because queries are visited in ascending as_of order and the version pointer only moves forward: O(V_k + Q_k) per chain. Binary search per query is the better shape only when Q is small relative to V and the versions are already indexed and resident.
- Return the empty answer as a distinct outcome. A query whose as_of precedes the first issued_ts means nothing was displayed, which is not the same as the earliest value, and is frequently the exact fact the review is chasing.
- Return a version later marked entered_in_error if it was live at the instant asked about. Reconstructing the past means reporting what was on the screen, including what was wrong, and quietly substituting today's truth defeats the purpose of the exercise.
Worked solution 45 min
- Choose P so a shard fits in memory: at P equal to 256, each shard holds about 781,000 versions and about 19,500 queries.
- Hash-partition versions and queries on the chain key, writing both to shard files.
- Per shard, sort versions by (chain key, issued_ts) and queries by (chain key, as_of_instant).
- Sweep the two sorted streams together, advancing the version pointer while issued_ts is at or before as_of and answering from the last version passed.
- Emit an explicit empty answer when the pointer has not advanced past any version for that chain, and concatenate the shard outputs.
Follow-up
- Read replicas lagged 40 seconds at the time. Does your answer describe what the clinician actually saw, and how would you bound the difference?
- Serve the same question online for a single chart at a p99 under 50ms. What changes?
- One partner changed its filler_order_id format mid-year, so the chain key is not stable. How does that appear in your output, and how do you detect it rather than returning empty answers?
Diagnose why an eligibility lookup ignores its index
A 40-million-row coverage_span serves eligibility lookups with: SELECT coverage_id, plan_id, coverage_order FROM coverage_span WHERE enterprise_person_id = $1 AND valid_to = 'infinity' AND effective_date <= $2 AND (termination_date IS NULL OR termination_date >= $2). An index exists on (effective_date). EXPLAIN (ANALYZE, BUFFERS) shows a sequential scan reading 1.8 million buffers at 2.4 seconds. Explain why that index cannot help, propose the index plus any schema change that makes the predicate index-friendly, and state what you expect the new plan and buffer count to be.
Approach
- Read the predicate for selectivity before touching the index. effective_date <= $2 matches most of a 40-million-row history, so a leading range scan on that column returns a large fraction of the table and the planner correctly prefers a sequential scan over millions of random heap fetches. The index is not being ignored; it is being rejected on cost.
- Put the equality column first. enterprise_person_id = $1 selects a handful of rows out of 40 million, so it must lead the composite index; a b-tree can only use columns after the first range predicate as filters, not as search bounds.
- Turn the open-ended termination into something indexable. The OR ... IS NULL branch forces either a BitmapOr or a post-index filter. Add coverage_end date GENERATED ALWAYS AS (COALESCE(termination_date, DATE '9999-12-31')) STORED and rewrite the predicate to coverage_end >= $2; both COALESCE over a column and a date literal are immutable, so the generated column is legal.
- Make the index partial on WHERE valid_to = 'infinity'. Most rows in a bitemporal table are superseded beliefs, so the partial index is a fraction of the full one, stays in cache, and encodes the predicate for free rather than re-checking it per row.
- Consider the range alternative honestly: a daterange(effective_date, termination_date + 1, '[)') column with a GiST index and the && operator handles overlap queries and supports an EXCLUDE constraint against overlapping active coverage, but GiST lookups are slower than b-tree for this point-in-time pattern. Pick b-tree for the read path and keep GiST only if you also need the overlap constraint.
- Verify rather than assert: re-run EXPLAIN (ANALYZE, BUFFERS) and read Rows Removed by Filter and the shared hit/read split, not just the total time.
Worked solution 30 min
- Run EXPLAIN (ANALYZE, BUFFERS) on the original and write down estimated versus actual rows for each predicate, which shows effective_date <= $2 is not selective.
- Add the generated column and rewrite the predicate to use coverage_end.
- Create the partial composite index and ANALYZE the table so the planner has statistics for the new column.
- Re-run EXPLAIN (ANALYZE, BUFFERS) and confirm an index scan with Rows Removed by Filter at or near zero.
- Measure the partial index size against the full-table equivalent with pg_relation_size to confirm the cache argument is real and not assumed.
Follow-up
- After the change the plan is still a sequential scan for one particular member. What single query tells you whether that is stale statistics, a genuinely huge row count for that person, or parameter-dependent plan caching?
- Your generated column needs backfilling on a live table. Does adding a STORED generated column rewrite it, and what does that mean for your maintenance window?
- How would you serve the same lookup when the answer must be as of a past system-time instant rather than the current belief, given your index is partial on valid_to = 'infinity'?
Add a backfilled NOT NULL column to a live encounter table
encounter holds 900 million rows and is written continuously by the admit/discharge/transfer consumer. You must add admission_source_code text, backfill it per row from a lookup on the source message archive, constrain it to a fixed code set, make it NOT NULL, and add an index on (facility_id, admit_ts) — with no write outage and no statement blocked longer than one second. Give the ordered steps, the lock level each takes, why the obvious single ALTER is unavailable here, and the session setting that stops a DDL statement from stalling everything behind it.
Approach
- Say why the shortcut does not apply. Since PostgreSQL 11, ADD COLUMN with a constant default is metadata-only and cheap, but this value is derived per row from an archive lookup, so there is no constant to store and the table must be written row by row regardless.
- Add the column nullable first: catalog-only, ACCESS EXCLUSIVE for microseconds. The danger is not the statement's duration but the lock queue — a pending ACCESS EXCLUSIVE blocks every reader that arrives behind it, so a long-running report turns a microsecond DDL into a multi-minute outage. SET lock_timeout = '1s' before the ALTER and retry on failure; that converts a potential outage into a retried statement.
- Deploy dual-write before backfilling. The consumer must populate the column on every insert and transfer from that moment, or the backfill chases a moving tail forever.
- Backfill in bounded batches over primary-key ranges, a few thousand rows per transaction with a pause between, restricted to WHERE admission_source_code IS NULL so it is resumable and idempotent. One giant UPDATE holds a transaction open for hours, pins the xmin horizon so autovacuum cannot clean anything, and bloats the table by a full row version per updated row.
- Add the value constraint as CHECK (...) NOT VALID first — ACCESS EXCLUSIVE, no scan — then ALTER TABLE ... VALIDATE CONSTRAINT, which takes only SHARE UPDATE EXCLUSIVE and scans without blocking reads or writes.
- Reach NOT NULL without a blocking scan: add CHECK (admission_source_code IS NOT NULL) NOT VALID, VALIDATE it, then SET NOT NULL, which on PostgreSQL 12 and later uses the validated constraint as proof and skips the full scan; drop the now-redundant CHECK afterwards. Build the index with CREATE INDEX CONCURRENTLY, which takes SHARE UPDATE EXCLUSIVE, makes two passes plus a wait for concurrent transactions, cannot run inside a transaction block, and leaves an INVALID index behind on failure that must be dropped and rebuilt.
Follow-up
- CREATE INDEX CONCURRENTLY has been running for four hours and you need to cancel it. What state is the index left in, how do you detect it, and what do you run next?
- Your backfill is at 40% and the replica is 90 seconds behind. What do you change, and what do you measure to decide the new batch size?
- The code set gains a value two weeks later. Does your CHECK constraint or a lookup table with a foreign key make that change cheaper, and what does each cost on the write path?
"How would you architect a notification queue system to deliver urgent…
"How would you architect a notification queue system to deliver urgent clinical alerts to care providers with zero message loss?"
Approach
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
"A client reports that a production server is experiencing unexplained…
"A client reports that a production server is experiencing unexplained latency after a software patch. Walk through your step-by-step troubleshooting methodology."
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
- How would you know your answer was wrong?
- What assumption would you test first?
Shape a bulk enrolment endpoint where rows fail independently
A payer partner submits weekly enrolment as one call carrying 10^4 to 2x10^6 member rows that become coverage_span (coverage_id, enterprise_person_id, payer_id, plan_id, subscriber_id, effective_date, termination_date, coverage_order, status, valid_from, valid_to, source_file_id). Some rows fail identity resolution, some carry a termination date before their effective date, and the rest apply cleanly. The partner's loader must be able to retry only what failed, and cannot fix the file until next week. Design the submission contract: synchronous or asynchronous, the response shape, how a row is addressed, and what retrying the failed subset does to rows that already applied.
Approach
- Size the call before shaping it. 2x10^6 rows will not survive a request/response cycle under any sane timeout, so the contract is asynchronous: upload, 202 with a job resource, poll or subscribe. All-or-nothing is equally wrong, because one malformed row would reject two million good ones and the partner cannot resubmit for a week.
- Give every row a caller-assigned stable identity - the partner's file id plus line number - so a result addresses back to the input without positional matching. Positional indexes break the instant the partner resubmits a filtered file, which is exactly what the retry is.
- Make the results a separate paginated resource, not an inline array: GET /enrolment-jobs/{id}/results?status=failed&cursor= returns one entry per failed row with a reason code and the offending field. An inline error array is unbounded, and two million failures is not a response body.
- Classify per row into applied, rejected and needs_review, and keep the last two addressable rather than dropping them. A row that failed identity resolution is not invalid data, it is an unresolved person, and it must be re-drivable after a manual link without the partner resubmitting anything.
- Make the row the unit of idempotency: UNIQUE (source_file_id, partner_line_number), so a resubmitted overlapping file is absorbed. State the one case that is not a duplicate - the same key with changed content must supersede, which in a bitemporal table means closing the prior version's valid_to and inserting a new row with valid_from set, leaving effective_date and termination_date as the partner asserted them rather than rewriting business time.
Worked solution 30 min
- Decide the transport and show the arithmetic that rules out a synchronous body at two million rows.
- Write the job resource schema: state, counts by outcome, started and finished timestamps, and links to the result pages.
- Write the per-row result schema with the caller's row identifier, the outcome, the reason code and the offending field.
- Write the row-level idempotency key and its constraint, then trace a retry of the failed subset against a job where 90 percent applied.
- Write what happens to a row whose content changed since it applied: the valid_to close-out and the new version, with business dates untouched.
Follow-up
- Midway the job hits a run of failures caused by our own dependency being down. Does the job fail, pause or keep going, and what does the partner see?
- The partner resubmits the entire file instead of the failed subset. What happens?
- How does the partner learn that a row applied and was later superseded by a newer file, without diffing two full exports?
Duplicate results appear only in the hour after interface restarts
About one in 40,000 ingested results produces a duplicate observation_result row. It happens only in the hour after an interface engine restarts and never reproduces in tests. The consumer checks a ledger table for the idempotency key, then writes the observation, then inserts the ledger row in a second transaction. The ledger has a unique index on the key, and the losing insert logs duplicate, ignoring. Adding a debug log between the check and the write made duplicates more frequent. Diagnose and fix.
Approach
- Run the query that splits the hypotheses before anything else: for one duplicate pair, compare their idempotency keys. Identical keys mean the key is right and mutual exclusion is broken. Different keys mean the key itself is wrong — a sender reusing or rotating control identifiers — which is a different bug with a different fix. Most time lost on this class of problem is lost by skipping this step.
- Measure the gap between the pair. created_at deltas in the tens of milliseconds, attributed to two different worker identifiers, mean two workers processed the same replayed message concurrently. Deltas of minutes or hours mean the ledger row was never durably written — rolled back, or written in a transaction that later aborted — and a later replay legitimately re-processed the message.
- Read the transaction boundaries rather than the isolation level. The check, the clinical write and the ledger insert span two transactions, so the window between the check and the ledger insert is unprotected at every isolation level, READ COMMITTED and SERIALIZABLE alike. The unique index protected the ledger; it never protected the observation, which had already committed by the time the conflict was detected.
- Explain why instrumentation moved it. The debug log widened the window between the check and the ledger insert, so more concurrent pairs landed inside it. Deleting the log narrows the window and hides the defect without fixing it — the same reason a single-threaded test suite is green, and the reason the incident correlates with restarts, when the broker redelivers a window of messages at once.
- Fix by turning check-then-act into one atomic conditional write: in a single transaction, INSERT INTO ingest_ledger (idem_key, ...) VALUES (...) ON CONFLICT (idem_key) DO NOTHING RETURNING idem_key, and if no row comes back, do nothing further. The observation insert belongs in that same transaction. Under READ COMMITTED the second inserter blocks on the unique index until the first commits and then sees the conflict, so the index — not the isolation level — supplies the mutual exclusion.
- Prove it with a deliberate concurrency test: two sessions released by a barrier on the same key, several thousand rounds, asserting exactly one observation row each time, then replay a captured restart window and diff row counts against a clean run.
Follow-up
- The clinical write must go to a different store than the ledger. How do you keep this atomic without a distributed transaction, and what does the outbox look like?
- The sender's control ID counter rolls over and begins reusing values. What breaks in your key, and what production signal would tell you it happened rather than a customer telling you?
Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Fix the scope and take a cold baseline
- Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
- Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
- Set the week's rule: one thing finished every night, including the night you only have forty minutes.
Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.
Practice prompt ↗Practice prompt ↗Worked solution ↗02One pattern, written three times from blank
- Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
- On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
- Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.
Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.
Practice prompt ↗Practice prompt ↗03One design, only to the depth you can defend
- Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
- Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
- Write the one tradeoff you are choosing against and the observation that would make you reverse it.
Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.
Practice prompt ↗Practice prompt ↗04Only the fundamentals you will have to defend
- Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
- Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
- Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.
Deliverable: Three written answers, each under two hundred words and each timed aloud.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Your own work, timed
- Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
- Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
- Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.
Deliverable: Two timed narratives with one defensible number in the opening line.
Practice prompt ↗Practice prompt ↗06The one full rehearsal, in the weekend block
- Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
- Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
- Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.
Deliverable: Mock notes naming three failure moments with a specific fix written under each.
Practice prompt ↗Practice prompt ↗07Taper
- Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
- Re-read only your own notes from this week and open no new material.
- Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.
Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Counting review comments or mentees proves nothing. The useful version is a specific change you approved with a reservation you stated, or one you blocked and the delay that cost. Say which standard you were holding and why it was worth the friction. A mentoring story needs the thing the other person can now do without you.
"Tell me four things that you are not, and explain how those traits in…
"Tell me four things that you are not, and explain how those traits influence your working style."
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
"Why do you want to work at Epic, and why are you interested in reloca…
"Why do you want to work at Epic, and why are you interested in relocating to Verona, Wisconsin?"
Approach
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
"You are faced with three competing client deadlines for custom softwa…
"You are faced with three competing client deadlines for custom software deployments. How do you prioritize these tasks and communicate updates to stakeholders?"
Approach
- Give the blast radius: what could have broken, and what you measured.
- Close with what you would do differently, concretely.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
- 01
"Tell me four things that you are not, and explain how those traits influence your working style."
- 02
"Why do you want to work at Epic, and why are you interested in relocating to Verona, Wisconsin?"
- 03
"You are faced with three competing client deadlines for custom software deployments. How do you prioritize these tasks and communicate updates to stakeholders?"
Is this an official Epic interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Epic. Rounds and questions reflect what candidates have reported, not a process Epic has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult is the Sphinx assessment, and how should I prepare for it?
The Sphinx assessment is widely considered challenging due to its breadth, speed math constraints, and requirement to write algorithmic pseudocode in a standard text box without a compiler. To prepare, practice LeetCode easy-to-medium array and backtracking problems in a plain text editor, practice rapid mental math and percentage estimations, and review sample logic word puzzles.
PracHub interview research ↗Does Epic allow remote work for Software Engineers?
No. Epic maintains an on-site work culture to foster rapid collaboration, mentorship, and team cohesion. All Software Engineer roles require full-time, on-site presence at Epic's headquarters in Verona, Wisconsin (near Madison). Relocation assistance packages are provided to hired candidates.
PracHub interview research ↗Do I need prior experience in healthcare IT or MUMPS/M programming to be hired?
No, prior healthcare background or experience with M/MUMPS is not required. Epic provides comprehensive onboarding and developer training programs that teach new hires company-specific technical stacks, internal framework architectures, and domain knowledge.
PracHub interview research ↗What is the typical timeline from initial application to offer?
The hiring process generally moves quickly, often taking between 2 to 4 weeks from application submission to final offer decision. Assessment results are typically evaluated within a few business days, followed promptly by scheduling for the final interview round.
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-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-24