How to approach machine coding interviews
A practical framework for machine coding rounds, from reading the problem to delivering compilable, well-structured code in a timed session.
The problem statement lands in your inbox. You have a bounded amount of time to build a working Splitwise-like clone from scratch. It must compile, run, and cover the stated behavior, while the evaluator can still understand the code.
A common failure mode is poor scoping: spending too long on perfect model classes, then rushing a giant main() method, or setting up a full framework before any business logic runs. The fix is a repeatable way to turn a problem statement into a small, working program under pressure.
This guide gives you that system: six phases, adaptable time budgets, and concrete signals of what to produce at each stage.
TL;DR
The purpose of this article is to help you deliver the smallest complete, runnable solution for the stated requirements, then use remaining time to harden and explain it.
- 30-second idea: scope the requirements, define the API, build one end-to-end path, compile often, add the highest-value validation, and finish with a readable demo.
- Mental model:
requirements β API β core model β happy path β validation β demo. - Repeatable process: keep a must-have checklist, implement the smallest vertical slice, verify it runs, then expand only where the requirements or an important failure mode justify it.
- Adapt to constraints: scale the phase budgets to the available time. If time shrinks, keep correctness and a runnable path; defer optional patterns, infrastructure, and polish.
What is a machine coding round?
A machine coding round is a timed coding exercise where you receive a problem statement and must produce compilable, runnable code that solves the stated scope. Think of it as a small take-home assignment completed live with a clock ticking.
Examples of real machine coding problems:
- Build a splitwise-like expense sharing app
- Implement a parking lot management system
- Create a task management board (like Trello)
- Build a snake-and-ladder game engine
- Implement a simple in-memory key-value store with TTL
The output is not only a design doc or a whiteboard sketch. It is runnable code that an evaluator should be able to compile and execute, followed by a review of how the implementation meets the requirements.
The most common surprise
It is easy to confuse this with an OOD round and produce class diagrams and design notes instead of runnable code. The evaluator needs to compile and execute the submission; if it does not compile, the core behavior cannot be assessed, regardless of how elegant the design looks on paper.
How it differs from OOD
If you've prepared for OOD interviews, many of the modeling skills transfer. The emphasis changes: OOD gives more space to discussion, while machine coding gives more weight to runnable, integrated code. The exact balance varies by exercise.
| Dimension | OOD interview | Machine coding round |
|---|---|---|
| Duration | Bounded, discussion-led | Bounded, implementation-led |
| Format | Live discussion with interviewer | Solo coding (often offline) |
| Primary output | Class diagram + key classes | Runnable core flow within the stated scope |
| Code expectation | Key classes and methods | Integrated implementation sized to the exercise |
| Must compile? | Nice to have | Non-negotiable |
| Design discussion | Extensive, verbal | Minimal, shown through code structure |
| Evaluation | Real-time by interviewer | Post-submission code review |
The biggest mental shift: in OOD, you explain more of the design verbally. In machine coding, your code carries much of that explanation. Every class name, method signature, and package boundary is a design decision the evaluator can inspect.
Candidates who are comfortable discussing patterns can still struggle to wire them into a running program under time pressure; candidates who never name a pattern can still produce clear code when the structure reflects the requirements.
The 6-phase framework
Use the following framework as a default for a machine coding problem. The example budget assumes a 90-minute round; scale it proportionally when the available time is different.
| Phase | Time | Output | You're behind if... |
|---|---|---|---|
| 1. Read and scope | 5 min | Annotated problem with must-haves circled | Still reading at minute 10 |
| 2. Entities and API | 10 min | Entity list + method signatures sketched | Can't name your top-level service method |
| 3. Core model | 15 min | Domain classes compiling | No code written by minute 30 |
| 4. Happy path | 20 min | One flow works end-to-end | Main method doesn't run at minute 55 |
| 5. Edge cases | 15 min | Validation and error handling | Adding features instead of hardening |
| 6. Demo | 5 min | Driver code showing multiple scenarios | No output printed to console |
The sample phases total 70 minutes in a 90-minute session. Reserve the remaining time for clarification, compilation, debugging, and polish rather than treating it as free feature time.
Phase 1: Read the problem carefully (5 minutes)
Read the entire problem statement before writing a single line. Misreading a requirement can send the implementation in the wrong direction and consume more time than a short planning pass.
On your first read, classify every requirement:
- Must-have: explicitly stated, will be tested. "Users can add expenses and split equally."
- Should-have: implied or mentioned casually. "Support different split types."
- Skip: anything involving persistence, UI, networking, or deployment. "Bonus: add a REST API."
Write the must-haves as a bullet list at the top of your code file. This becomes your checklist. Every 20 minutes, glance at it and verify you're building what was asked.
The 80/20 of problem reading
Many machine coding problems have a small set of core requirements and a few stretch goals. Complete the core requirements before touching stretch work. A smaller solution with reliable behavior is easier to assess than a broader solution with unfinished paths.
Phase 2: List entities and define the API (10 minutes)
Turn the problem statement into nouns (entities) and verbs (operations). For an expense-sharing app:
Entities: User, Expense, Split, ExpenseType (EQUAL, EXACT, PERCENT)
API (the public methods on your service):
addUser(userId, name, email)addExpense(paidBy, amount, splitType, participants, splits)showBalances(userId)
Don't build interfaces for everything. Don't create abstract factories. Write down the concrete classes you need and the methods they expose. This is your blueprint, and you have 10 minutes for it.
Keep this list as a comment block at the top of the main file or in a nearby note. It helps maintain focus when implementation reveals tempting but optional features.
Phase 3: Build the core model (15 minutes)
Now write the domain classes. Start with enums and value objects since they're small, they compile instantly, and they give you the vocabulary for everything else.
Three rules for model classes in machine coding:
- Keep fields minimal. Only add what you'll actually use in logic. If no method reads
createdAt, don't add it. - Use records or simple classes. Java records are perfect here. Four lines, immutable, done.
- Compile after each small unit. Catch typos while the affected class or method group is still easy to isolate.
// Compiles in 10 seconds. Move on.
public enum ExpenseType { EQUAL, EXACT, PERCENT }
public record User(String userId, String name, String email) {}
public record Split(String userId, double amount) {}
By the end of this phase, the model classes should compile and the editor should show no unresolved errors. This is the key checkpoint before layering on business logic.
Phase 4: Implement the happy path (20 minutes)
This is the largest block and the most dangerous. The temptation is to handle every edge case as you go. Resist it. Build one complete flow, end to end, with zero error handling. Get something that runs.
For the expense-sharing example, the happy path is: add two users, create an equal-split expense, show balances. That's it. If this works, you have a foundation to build on.
Write a service class that implements the core operations. Wire it up with a main() method that calls those operations and prints output. Compile. Run. See output on the console.
public class ExpenseService {
private final Map<String, User> users = new HashMap<>();
private final Map<String, Double> balances = new HashMap<>();
public void addUser(String userId, String name, String email) {
users.put(userId, new User(userId, name, email));
balances.put(userId, 0.0);
}
public void addExpense(String paidBy, double amount,
ExpenseType type, List<String> participants) {
if (type == ExpenseType.EQUAL) {
double share = amount / participants.size();
for (String uid : participants) {
if (!uid.equals(paidBy)) {
balances.merge(uid, -share, Double::sum);
balances.merge(paidBy, share, Double::sum);
}
}
}
}
public void showBalances(String userId) {
double bal = balances.getOrDefault(userId, 0.0);
if (Math.abs(bal) < 0.01) {
System.out.println(userId + ": settled up");
} else {
System.out.printf("%s: %s%.2f%n", userId, bal > 0 ? "is owed " : "owes ", Math.abs(bal));
}
}
}
Notice what's missing: no EXACT or PERCENT split handling, no input validation, no error messages. That's intentional. Those come next.
If the happy path is not producing correct output by roughly the midpoint, pause feature work and make that path run. A running program with limited features is more useful than a comprehensive program that cannot execute.
The compilation trap
Writing a large block without compiling lets small syntax and type errors accumulate. Compile every 5-10 minutes, or after a meaningful vertical slice, so failures remain local and easy to diagnose.
Phase 5: Add edge cases and validation (15 minutes)
Now harden the code. Go through your requirements checklist and add:
- Input validation (null checks, invalid IDs, negative amounts)
- Additional split types (EXACT, PERCENT) if required
- Error messages that are human-readable
- Boundary conditions (splitting among one person, zero-amount expenses)
Prioritize by what the evaluator is likely to test. If the problem says "support three split types," implement all three before optional features. If it says "handle invalid input gracefully," include representative invalid inputs in the validation pass.
Phase 6: Demonstrate with driver code (5 minutes)
Your main() method is your demo reel. It should tell a story:
public static void main(String[] args) {
ExpenseService service = new ExpenseService();
// Setup users
service.addUser("u1", "Alice", "alice@example.com");
service.addUser("u2", "Bob", "bob@example.com");
service.addUser("u3", "Charlie", "charlie@example.com");
// Scenario 1: Equal split
System.out.println("--- Equal split: Alice pays 300 for lunch ---");
service.addExpense("u1", 300, ExpenseType.EQUAL, List.of("u1", "u2", "u3"));
service.showBalances("u1");
service.showBalances("u2");
// Scenario 2: Show that balances accumulate
System.out.println("\n--- Another expense: Bob pays 600 ---");
service.addExpense("u2", 600, ExpenseType.EQUAL, List.of("u1", "u2", "u3"));
service.showBalances("u1");
service.showBalances("u2");
}
The driver code does three things: demonstrates the app, shows the evaluator how to use your API, and exercises more than one scenario. Name your scenarios with comments and print section headers so the output is scannable.
30-second and 5-minute explanations
30-second explanation
"I will first scope the must-have behavior and write the public API. Then I will build the smallest domain model, make one happy path run end to end, compile frequently, and add validation for the requirements that are most likely to be exercised. I will finish with a small driver that demonstrates success and an important failure case."
5-minute explanation
Start by separating must-haves from stretch goals and writing the input/output contract. Sketch the core entities and implement a vertical slice: construct the model, call the service, and print a result. Once that path runs, add required variations such as split types, domain validation, and clear errors. Keep abstractions at real variation points, use the time checkpoints to drop optional work, and use the driver to trace multiple scenarios. The trade-off is deliberate: a smaller complete solution is easier to verify than a broad but unfinished architecture.
Code quality signals
Your code will be read by a human, line by line. Here's what they're looking for:
| Signal | Why it matters | Quick win |
|---|---|---|
| Meaningful names | calculateShare() tells the evaluator what the method does. calc() tells them nothing. | Spend 5 extra seconds naming every method. |
| Small methods | A 50-line method is hard to read and review. Five 10-line methods with clear names are self-documenting. | Extract any block with an if or loop into a named method. |
| Package structure | Putting all classes in one file is acceptable but putting them in model/, service/, strategy/ packages shows you think about organization. | Create 2-3 packages. Even for a small project, it signals professionalism. |
| SOLID principles | You don't need to name them explicitly. If your service depends on an interface rather than a concrete class, the evaluator notices. | Use one interface for the most important variation point. |
| Enum over strings | ExpenseType.EQUAL cannot be misspelled. "equal" can. | Convert every fixed set of values into an enum immediately. |
One principle to prioritize above the rest in machine coding is readability over cleverness. A straightforward for loop can be better than a chain of .stream().map().flatMap().collect() when the latter obscures the logic. The code should be understandable to a reviewer who did not design it and has limited time to inspect it.
What to skip
Time is the scarcest resource. Here's what you should explicitly not build:
| Skip this | Why |
|---|---|
| Database or file persistence | In-memory HashMap is perfectly fine. The evaluator cares about logic, not storage. |
| REST API or HTTP layer | Unless the problem specifically asks for it, a main() method is your API. |
| Authentication and authorization | Out of scope for machine coding. Mention it in comments if you want. |
| Complex UI | Console output is enough. Don't build a Swing app or HTML page. |
| Framework boilerplate | No Spring Boot, no dependency injection frameworks. Plain Java is faster. |
| Comprehensive error handling | Validate the obvious (null, negative amounts). Don't build a custom exception hierarchy. |
| Unit tests | If tests are not explicitly required, keep the feedback loop lightweight. Do not let test setup crowd out core behavior; if tests are required, write the smallest useful set. |
Setting up a Spring Boot project with Gradle, JPA entities, and repository interfaces can consume the available time before any business logic runs. Unless the exercise explicitly requires that infrastructure, keep the solution in memory and focused on the domain behavior.
The 'mention and move on' technique
When you skip something deliberately, add a one-line comment: // In production: would add input validation for email format. This tells the evaluator you know it's missing and chose to skip it, rather than forgetting it exists.
Trade-offs and adapting to constraints
The framework gives you a starting budget, but the actual exercise may be shorter, longer, or interrupted by clarification questions. Adapt by protecting the runnable core and spending optional time only where it reduces risk.
When you're ahead of schedule: Don't add features. Improve what you have. Better variable names, extract a helper method, add a second scenario to your driver code. The evaluator will appreciate polished core logic more than half-baked extra features.
When you're behind schedule: Drop optional edge cases and go straight to the demo. A working program that handles the happy path gives the evaluator something concrete to assess; a non-compiling program cannot demonstrate its behavior.
When you're stuck on a bug: Set a 5-minute timer. If you can't fix it in 5 minutes, comment out the broken code, add a // TODO: fix split calculation comment, and move on. A program that runs with one feature missing is better than a program that doesn't run because of one bug.
The 60-minute checkpoint is your decision point in a 90-minute round. After 60 minutes, everything you do should converge toward a running demo. No new features. No new classes. Just make it work and make it readable.
Common pitfalls
1. Starting without any design
The opposite extreme of over-designing is opening the IDE and typing public class Main as the first action. Even a short pass listing entities and sketching method signatures can prevent a costly model change later.
2. Gold-plating the model layer
Candidates who know design patterns well tend to over-engineer the model. They build abstract base classes, generic repositories, factory hierarchies, and visitor patterns before writing any business logic. Your User class doesn't need to implement Serializable, Comparable, and a custom Builder. It needs three fields and a constructor.
3. Not compiling frequently
If you write a large block of Java and hit compile for the first time only at the end, several errors may mask one another and take substantial time to untangle. Compile after each class or meaningful method group. The feedback from frequent compilation is usually more valuable than another optional pattern.
4. Handling edge cases before the happy path
"What if the amount is negative? What if the user doesn't exist? What if the split doesn't add up to 100%?" These are valid concerns, but addressing them before you have a working happy path is a time trap. Build the straight line through the system first. Add guardrails later.
5. Over-using design patterns
Machine coding evaluators want to see clean, working code, not a showcase of Gang of Four patterns. If your expense sharing app uses Strategy, Observer, Factory, Builder, and Decorator, the evaluator will wonder why a simple problem needed five patterns. Use one or two patterns where they genuinely reduce complexity. Keep the rest straightforward.
6. Forgetting the driver code
Your submission might be evaluated by someone who won't read your code first. They'll run main(), look at the output, and then dig into the source if the output looks correct. If main() prints nothing, or prints unformatted garbage, the first impression is terrible. Invest 5 minutes in clean driver code with labeled scenarios and readable output.
Machine coding evaluation rubric
Understanding common evaluation dimensions helps you allocate time wisely. The exact rubric varies, but a useful priority order is:
| Dimension | Priority | What they look for |
|---|---|---|
| Correctness | Highest | Does it handle the stated requirements? Does the output match expected results? |
| Code structure | High | Are classes focused? Are responsibilities separated? Is the code navigable? |
| Readability | High | Naming, formatting, method sizes, comments where non-obvious |
| Extensibility | Medium | Could a stated or likely variation be added without rewriting the core? |
| Completeness | Medium | Edge cases, validation, and explicitly requested extras |
Correctness and structure are foundational. A program that runs correctly with clear class separation is easier to assess and extend than one that handles many edge cases inside a single large class.
For your interview: if you have to choose between "add one more feature" and "split this large class into two smaller ones," pick the refactor. Code structure points are easier to earn and harder to lose.
Comparing OOD and machine coding preparation
If you've already prepared for OOD interviews, here's how to adjust your approach for machine coding.
| Skill | OOD value | Machine coding value | Adjustment |
|---|---|---|---|
| Identifying entities | High | High | Same skill, directly transferable |
| Drawing class diagrams | High | Low | Skip diagrams. Your code IS the diagram. |
| Verbalizing trade-offs | High | Low | Write clean code instead of explaining it |
| Writing compilable code | Medium | Critical | Practice timed coding. A lot. |
| Time management | Important | High | Use checkpoints and protect the runnable core. |
| Design patterns (named) | High | Medium | Use patterns, but don't name them unless asked |
| Driver/demo code | Not needed | Essential when an execution entry point is required | Keep a focused main() or equivalent demo |
How to practice
The best practice for machine coding is simulated time pressure. Set a 90-minute timer and build one of these:
- Expense sharing app (Splitwise clone) - covers: multiple split strategies, balance tracking
- Parking lot system - covers: spot assignment, ticket lifecycle
- Snake and ladder game - covers: game state, turn management, board configuration
- In-memory key-value store with TTL - covers: data structures, time-based expiry
- Task management board - covers: state transitions, user assignment, filtering
After the timer stops, review your own code as if you're the evaluator. Ask yourself: does it compile? Does main() demonstrate the core features? Can I understand each class in under 30 seconds? Would I want to add a feature to this codebase?
The post-practice ritual
After each practice session, write down two things: what you should have skipped and where you lost the most time. Repeating this review builds a personal list of time traps and a better sense of what fits in the chosen time budget.
Test Your Understanding
Q1. What should be working before you add stretch goals?
Answer: One core use case should run end to end, with a clear API and visible output.
Q2. Why compile frequently in a timed exercise?
Answer: Frequent builds keep syntax and type errors local, so they are cheaper to diagnose.
Q3. When is an abstraction justified?
Answer: When the requirements contain a real variation point or a boundary that needs independent testing; otherwise, prefer direct code.
Q4. What do you cut first when time is short?
Answer: Optional features, infrastructure, and polish. Keep the required behavior, validation that protects it, and a runnable demo.
Q5. What should the driver demonstrate?
Answer: A successful core scenario and at least one meaningful boundary or failure case, using readable output.
Quick recap
- Machine coding rounds require compilable, runnable code. If it doesn't compile, the design doesn't matter.
- Follow the 6-phase framework: Read and Scope, Entities and API, Core Model, Happy Path, Edge Cases, Demo.
- Compile every 5-10 minutes, or after a meaningful vertical slice. Frequent feedback is one of the most useful disciplines in timed coding.
- Build the happy path first. A working program with limited features beats a broken program with comprehensive features.
- Skip persistence, frameworks, UI, and test setup unless the prompt requires them. In-memory data structures, a small test loop, and console output are often sufficient for a logic-focused exercise.
- Invest 5 minutes in clean driver code with labeled scenarios. It's your evaluator's first impression.
- When in doubt at the 60-minute mark, stop adding and start polishing. Make it run, make it readable, and make the scope explicit.
Related concepts
- OOD interview approach covers the discussion-first design process that precedes implementation.
- Writing clean code focuses on names, method boundaries, error contracts, and readable structure.
- Choosing design patterns explains how to introduce abstractions only when a requirement creates a real design force.
Related Articles
A step-by-step framework for object-oriented design interviews, from clarifying requirements to implementing clean, extensible code in a bounded session.
Write readable interview code with meaningful names, small focused methods, clear structure, and comments that explain decisions, not syntax.