OOP & Language Internals
- Answer the OOP and language-internals questions that decide SAP technical rounds, in Java and C++
- Defend the distinctions interviewers push on: overriding vs overloading, interface vs abstract class, fail-fast vs fail-safe
- Design a clean class under time: LRU Cache and a Premium question with O(1) operations
- Coordinate two threads to produce an ordered sequence, the concurrency question SAP keeps asking
Most candidates prepare for SAP the way they prepare for everyone else: grind hard graphs and DP, memorize a few templates, hope a medium shows up. Then the interviewer opens with "what are the four pillars of OOP," asks you to explain a virtual function, and wants to know exactly what happens when two threads touch the same list. This is the round people fail. One candidate answered every coding question correctly at an onsite in Palo Alto and still got rejected; the gate was fundamentals and fit.
The coding on SAP loops is easy to medium and rarely the deciding factor. What separates offers from rejects is whether you can talk about the language you write in. Both Java and C++ come up, often in the same interview, and the questions are the textbook ones you stopped reviewing years ago. Overriding versus overloading, interface versus abstract class, throw versus throws, the final keyword, what the JVM and JRE are, how exceptions are implemented. Interviewers also lean hard on threads and concurrency once you have any experience. The fundamentals below come first, then the three design and concurrency problems that show up most.
SAP interviewers have said outright that they prefer clean, modular, readable code over a clever solution to a harder problem, and that they are not chasing the most optimized answer. Think out loud, write something you can explain, and be ready to defend every language choice you make. A plain solution you understand cold beats a dense one you cannot reason about under questions.
What this round drills
These are the questions that recur across SAP Labs and SAP America reports, grouped by theme. None of them is exotic. The failure mode is being fluent in algorithms and vague about the language. Treat each one as something you can explain in two clear sentences, ideally with a one-line code example ready.
OOP pillars and polymorphism
4 questionsThe opener on nearly every technical round. Know the four pillars and be ready to demonstrate one in code, not just name it.
What is OOP and what are its four pillars?
Encapsulation, abstraction, inheritance, polymorphism. Expect a follow-up asking you to demonstrate one, "show me encapsulation in a language of your choice."
What is a virtual function in C++, and when do you need one?
A base-class function marked virtual so a call through a base pointer or reference dispatches to the derived override at runtime. Without it the call binds statically to the base version.
Explain polymorphism and its types.
Compile-time (overloading) versus run-time (overriding, dynamic dispatch). Interviewers ask you to separate the two cleanly.
How do you achieve multiple inheritance in Java?
Java has no multiple class inheritance; you compose behavior through multiple interfaces (and, since Java 8, default methods). C++ allows it directly, which invites the diamond-problem follow-up.
Language internals (Java and C++)
4 questionsThe part candidates skip. SAP asks what the runtime is doing underneath the syntax.
What are the JRE and the JVM, and how do they differ?
The JVM executes bytecode; the JRE is the JVM plus the standard libraries needed to run a program. The JDK adds the compiler and tools on top.
What does the final keyword do in Java?
A final variable cannot be reassigned, a final method cannot be overridden, and a final class cannot be extended. Expect all three cases, not just the variable one.
What are classes and objects?
A basic screen for whether you can define terms precisely under pressure; a class is the blueprint, an object is an instance with its own state.
Dry-run this snippet and tell me the output.
Output-based questions on inheritance, static binding, and constructor order appear in both the live round and the OA MCQs. Trace state carefully rather than guessing.
Exceptions and error handling
2 questionsA recurring stumble. Candidates who freeze here often say later it was the question that sank them.
How are exceptions implemented in Java versus C++?
Both unwind the stack to a matching handler. Java splits checked from unchecked exceptions and enforces checked ones at compile time; C++ has no checked-exception concept.
What is the difference between throw and throws in Java?
throw raises an exception instance at a point in code; throws is a method-signature clause declaring which checked exceptions the method may propagate.
Collections and iterators
2 questionsWhere the Java library meets concurrency. Common once you have a year or two of experience.
What are fail-fast and fail-safe iterators?
Fail-fast iterators throw ConcurrentModificationException if the collection is structurally modified during iteration; fail-safe iterators work over a copy or snapshot and never throw, at the cost of possibly stale reads.
What happens when more than one thread acts on a list, and how do you make it thread-safe?
Plain ArrayList corrupts under concurrent writes. Reach for synchronization, Collections.synchronizedList, or a CopyOnWriteArrayList, and be able to say when each fits.
Concurrency and threads
3 questionsWeighted toward experienced loops. The coordination question below shows up almost verbatim.
Two threads must print numbers 1 through 10 in order, one handling odds and one handling evens. How do you coordinate them?
A synchronization question, not an algorithm one. Solved with a shared lock and a turn variable, or wait/notify. Worked end to end later in this lesson.
What is the difference between a semaphore and a mutex?
A mutex is ownership-based mutual exclusion for one thread at a time; a semaphore is a counter permitting up to N concurrent holders and needs no single owner.
What is ExecutorService and why use it over raw threads?
It pools and reuses threads and decouples task submission from execution, so you are not spawning a thread per task by hand.
The distinctions they make you defend
SAP interviewers rarely accept a one-word answer to these. They ask for the difference, then a case where you would use each. The three pairs below come up the most, so rehearse them until you can give the distinction and an example without pausing.
Overriding
- Run-time polymorphism: the method that runs is chosen by the object's actual type when the call is made.
- Same name and same signature as the parent method; the subclass replaces the inherited behavior.
- Across a class hierarchy: a subclass overriding a superclass method. In C++ the base method must be
virtualfor this to dispatch dynamically. - Use it to specialize behavior polymorphically, one base type, many concrete implementations behind it.
Overloading
- Compile-time polymorphism: the method is chosen from the argument types the compiler sees.
- Same name, different parameter list (count, types, or order). Return type alone does not overload.
- Within a single class (or an inherited set), several methods sharing a name for related operations.
- Use it for convenience variants of one operation,
print(int),print(String),print(double).
Interface
- A pure contract: method signatures a class promises to implement (plus default and static methods since Java 8).
- A class can implement many interfaces, which is how Java gets multiple inheritance of type.
- No instance state; fields are implicitly
public static finalconstants. - Reach for it when unrelated classes must share a capability,
Comparable,Runnable, without sharing a lineage.
Abstract class
- A partial implementation: it can mix abstract methods with concrete ones and hold real state.
- A class can extend one abstract class, the single-inheritance rule still applies.
- Can have constructors, instance fields, and any access modifier on its members.
- Reach for it when related classes share code and state, a common base with some behavior filled in and some left abstract.
Fail-fast iterator
- Iterates over the live collection and tracks a modification count.
- Throws
ConcurrentModificationExceptionthe moment the collection is structurally changed mid-iteration. - The default for
ArrayList,HashMap, and mostjava.utilcollections. - Best effort, not a guarantee, so never rely on it as a correctness check; treat it as a bug detector.
Fail-safe iterator
- Iterates over a copy or snapshot of the collection's state.
- Never throws on concurrent modification; changes made during iteration simply are not seen.
- Used by concurrent collections like
CopyOnWriteArrayListandConcurrentHashMap. - The trade-off is a possibly stale view and the memory cost of the snapshot.
Asked at SAP
These three are the coding questions this round leans on, and each tests something beyond an algorithm. The two design problems reward a clean class with a tidy API, exactly the "readable over clever" bar SAP states. The concurrency problem is the two-thread coordination question in the battery above, worked end to end.
LRU Cache: O(1) Get and Put
Problem
capacity and supports two operations, both of which must run in O(1) average time:-
get(key) returns the value for key if present, otherwise -1. A successful get counts as a use.-
put(key, value) inserts or updates the value for key. If the cache is at capacity, evict the least recently used key first.Any get or put makes that key the most recently used. When an insert overflows the capacity, the key that has gone longest without a get or put is the one that leaves.
Input: capacity = 2; put(1,1), put(2,2), get(1), put(3,3), get(2), put(4,4), get(1), get(3), get(4)
Output: 1, -1, -1, 3, 4
put(3,3) evicts key 2 (least recently used), so get(2) is -1. put(4,4) evicts key 1, so get(1) is -1. Keys 3 and 4 remain.
Combine a hash map for O(1) lookup with a doubly linked list for O(1) recency reordering. The map points key to node; the list keeps nodes in use-order from most to least recent.
LRU Cache: O(1) Get and Put
Click "Run" to execute your code
Test Cases
[["LRUCache",[2]],["put",[1,1]],["put",[2,2]],["get",[1]],["put",[3,3]],["get",[2]],["put",[4,4]],["get",[1]],["get",[3]],["get",[4]]]Expected: [null,null,null,1,null,-1,null,-1,3,4][["LRUCache",[1]],["put",[2,1]],["get",[2]],["put",[3,2]],["get",[2]],["get",[3]]]Expected: [null,null,1,null,-1,2][["LRUCache",[2]],["put",[1,10]],["put",[1,20]],["get",[1]],["put",[2,2]],["put",[3,3]],["get",[1]],["get",[2]]]Expected: [null,null,null,20,null,null,-1,2]A Premium question
Problem
push, pop, and top like a normal stack, plus a getMin operation that returns the smallest element currently in the stack. Every one of the four operations must run in O(1).You can assume
pop, top, and getMin are only called on a non-empty stack.Input: push(-2), push(0), push(-3), getMin(), pop(), top(), getMin()
Output: -3, 0, -2
getMin returns -3 (the smallest). After pop removes -3, top is 0 and getMin returns -2.
The catch is O(1) getMin, so you cannot scan on each call. Track the running minimum alongside the values, so the current minimum is always available at the top.
A Premium question
Click "Run" to execute your code
Test Cases
[["MinStack",[]],["push",[-2]],["push",[0]],["push",[-3]],["getMin",[]],["pop",[]],["top",[]],["getMin",[]]]Expected: [null,null,null,null,-3,null,0,-2][["MinStack",[]],["push",[5]],["push",[5]],["push",[3]],["getMin",[]],["pop",[]],["getMin",[]],["pop",[]],["getMin",[]]]Expected: [null,null,null,null,3,null,5,null,5][["MinStack",[]],["push",[2]],["push",[1]],["push",[4]],["top",[]],["getMin",[]],["pop",[]],["top",[]],["getMin",[]]]Expected: [null,null,null,null,4,1,null,1,1]Alternating Threads: Print 1..N in Order
Problem
1 through n in order. One thread is responsible only for odd numbers, the other only for even numbers, and they must hand off so the combined output is exactly 1, 2, 3, ..., n.Implement
print_in_sequence(n) so that it starts both threads and returns the list of numbers in the order they were emitted. With correct coordination the returned list is always [1, 2, ..., n].This is a synchronization problem: the challenge is making two threads take strict turns.
Input: n = 10
Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
The odd thread emits 1, 3, 5, 7, 9 and the even thread emits 2, 4, 6, 8, 10, each waiting for its turn so the interleaving is strictly ascending.
Share a lock and a single "next number" counter between the threads. Each thread waits until the next number matches its parity, emits it, advances the counter, and wakes the other thread.
Alternating Threads: Print 1..N in Order
Click "Run" to execute your code
Test Cases
[10]Expected: [1,2,3,4,5,6,7,8,9,10][1]Expected: [1][2]Expected: [1,2][7]Expected: [1,2,3,4,5,6,7]Priority Job Queue: Heap Plus an Id Index for Delete
Problem
JobQueue, which holds jobs waiting to run. Each job carries an integer jobId, unique among the jobs currently waiting, and an integer priority, where a larger number means the job runs sooner. Five operations:-
insert(jobId, priority) queues a job at that priority.-
remove(jobId) drops whichever job carries that id and returns true. When no waiting job carries it, nothing changes and the answer is false.-
top() returns the id of the highest-priority waiting job, or -1 while the queue is empty.-
list() returns every waiting id ordered from top priority down. Jobs tied on priority come back in insertion order.-
size() returns how many jobs are waiting.Aim for O(log n) on
insert and remove, and O(1) on top and size.Input encoding. A test case is a list of operations. The first entry,
["JobQueue", []], constructs the queue, and each later entry names a method with its argument list. The expected output lists the return value of every call, using null for the constructor and for each insert.Input: insert(7,5), insert(3,9), insert(11,1), top(), size(), list(), remove(3), top()
Output: 3, 3, [3, 7, 11], true, 7
Job 3 holds priority 9, the highest of the three, so it leads both the top call and the ordered list. Dropping it by id promotes job 7 at priority 5.
Input: insert(1,5), insert(2,5), insert(3,5), top(), remove(1), top()
Output: 1, true, 2
All three tie at priority 5, so arrival order decides. Job 1 leads, and once it leaves the queue job 2 takes over.
A binary heap answers "who runs next" in constant time and reorders in O(log n), yet it has no way to find an arbitrary job. Carry a hash map from job id to that job's current index in the heap array, refresh it on every swap, and deletion becomes swap-with-last plus one sift.
Priority Job Queue: Heap Plus an Id Index for Delete
Click "Run" to execute your code
Test Cases
[["JobQueue",[]],["insert",[7,5]],["insert",[3,9]],["insert",[11,1]],["top",[]],["size",[]],["list",[]],["remove",[3]],["top",[]],["list",[]]]Expected: [null,null,null,null,3,3,[3,7,11],true,7,[7,11]][["JobQueue",[]],["top",[]],["size",[]],["list",[]],["insert",[42,2]],["remove",[99]],["remove",[42]],["size",[]],["top",[]],["list",[]]]Expected: [null,-1,0,[],null,false,true,0,-1,[]][["JobQueue",[]],["insert",[1,5]],["insert",[2,5]],["insert",[3,5]],["top",[]],["remove",[1]],["top",[]],["remove",[3]],["top",[]],["list",[]]]Expected: [null,null,null,null,1,true,2,true,2,[2]][["JobQueue",[]],["insert",[10,1]],["insert",[20,8]],["insert",[30,3]],["insert",[40,7]],["insert",[50,2]],["insert",[60,6]],["insert",[70,4]],["top",[]],["remove",[20]],["top",[]],["remove",[70]],["list",[]],["size",[]],["remove",[10]],["top",[]],["list",[]]]Expected: [null,null,null,null,null,null,null,null,20,true,40,true,[40,60,30,50,10],5,true,40,[40,60,30,50]][["JobQueue",[]],["insert",[5,0]],["insert",[6,-3]],["top",[]],["remove",[5]],["top",[]],["insert",[5,10]],["top",[]],["list",[]],["size",[]]]Expected: [null,null,null,5,true,6,null,5,[5,6],2][["JobQueue",[]],["insert",[1,1]],["insert",[2,2]],["remove",[2]],["remove",[1]],["size",[]],["top",[]],["insert",[3,3]],["top",[]],["remove",[3]],["list",[]],["size",[]]]Expected: [null,null,null,true,true,0,-1,null,3,true,[],0]POST /jobs with the id and priority in the body, DELETE /jobs/{jobId}, GET /jobs/top, GET /jobs?sort=priority for the ordered list, and GET /jobs/count. Be ready to defend two of those choices: keying the delete on an identifier in the path, and giving the highest-priority read its own route. An SAP grader reads REST instinct alongside the algorithm.Part b asks how the data stays consistent, which is answered at the system level. Jobs belong in a store that survives a restart, and writes for one queue belong behind a single owner so two schedulers cannot both hand out the same top job. A worker that claims a job takes a lease with a visibility timeout, which puts the job back when that worker dies mid-run. Raise idempotency on insert as well, since a client retrying a timed-out POST should not end up with the job queued twice, and a caller-supplied identifier makes that retry safe to accept.
Blocking Queue With Two Producers and Two Consumers: Why synchronized Is Not Enough
Problem
capacity items at a time. Four threads share it.- Producer TRUE walks
flags left to right and pushes one item for every 1 it finds. The item carries that flag's position and the tag true.- Producer FALSE does the same for every
0, tagging its items false.- Consumer TRUE takes only
true items, Consumer FALSE only false ones. A consumer may take the item at the head of the buffer and nothing else, and it appends that item's position to its own log.A push blocks while the buffer already holds
capacity items. A take blocks while the buffer is empty or its head carries the other tag. Each consumer finishes once it has handled every item of its own tag, so no sentinel value is needed to shut it down.Write
run_blocking_queue(flags, capacity). Start all four threads, wait for them, and return [true_log, false_log]. Under correct coordination true_log holds the positions of the 1s in order and false_log the positions of the 0s. Spinning on the state without releasing the lock does not count as a solution.Every entry of
flags is 0 or 1, and capacity is at least 1.Input: flags = [1,0,1,1,0,0,1], capacity = 2
Output: [[0, 2, 3, 6], [1, 4, 5]]
Positions 0, 2, 3 and 6 hold a 1, so the true consumer logs those in order, and the false consumer logs 1, 4 and 5. Two slots means a producer that runs ahead of its consumer has to wait for a take before it can push again.
One lock guards the buffer, and three condition variables hang off that same lock. Producers park on the first until a slot opens. Each consumer gets its own, signalled only when the item at the head carries that consumer's tag. Every wait belongs inside a while loop.
Blocking Queue With Two Producers and Two Consumers: Why synchronized Is Not Enough
Click "Run" to execute your code
Test Cases
[[1,0,1,1,0,0,1],2]Expected: [[0,2,3,6],[1,4,5]][[1,0],1]Expected: [[0],[1]][[0,0,1,0],3]Expected: [[2],[0,1,3]][[1,1,1,1,0],1]Expected: [[0,1,2,3],[4]][[1,0,0,1],10]Expected: [[0,3],[1,2]][[0,1,1,0,1,0,0,1,1,0,1,0],3]Expected: [[1,2,4,7,8,10],[0,3,5,6,9,11]]synchronized block, the interviewer stayed unsatisfied, and the follow-up ask was the same thing on explicit Locks. SAP's platform code is full of worker pools feeding typed handlers, and the round turns on wait/notifyAll over one monitor waking every parked thread, including the three that cannot act. Two Condition objects on a single ReentrantLock answer that.ArrayBlockingQueue hands you the bounded buffer and both waits already written, and an ExecutorService gives you the four workers without touching Thread directly. A hiring-manager round tends to push that way once the hand-rolled version is on the board. The generalization also shows up, five threads printing one item each in a fixed cycle, which is the same lock with one Condition per position in the cycle. Have an answer ready for shutdown too, since a poison-pill sentinel is how you stop consumers that cannot count their own quota in advance.Key Takeaways
- ✓This round decides SAP loops, and it turns on fundamentals. Candidates have aced every coding question and still been rejected on OOP and language internals.
- ✓Explain the whole battery in two sentences with an example, in both Java and C++. That means OOP pillars, virtual functions, overriding vs overloading, interface vs abstract class, throw vs throws, final, JRE vs JVM, and fail-fast vs fail-safe iterators.
- ✓Design questions (LRU Cache, a Premium question) reward a clean class with the right structures: a hash map plus doubly linked list for LRU, values paired with their running minimum for a Premium question. State the plan before you code.
- ✓Concurrency is a real surface once you have experience. The two-thread ordered-print question is solved with a shared lock and a turn variable, waiting in a loop and notifying on completion, never with a busy-wait or a file hack.
The next lesson and 5 more SAP lessons are Premium. $49 a year, less than $1 a week · Cancel anytime
SAP interview guideKnow what SAP asks before you walk in.
16 coding · 2 system design · 35 behavioral questions
Get their questions first.
InterviewLegend is independent and not affiliated with or endorsed by any company it covers.