Java Full Stack Developer Top 100 Interview Questions
100 Java full stack developer interview questions with structured answers, comparison tables, flow diagrams and runnable examples. Covers core Java, concurrency, backend architecture, REST APIs, SQL, persistence, HTML, CSS, JavaScript, security, testing, deployment and system design.
Questions 61-80 of 100
Q61.What causes an ORM N+1 query problem, and how would you fix it?
An N+1 problem occurs when loading a collection of N parent records triggers additional queries for related data, often one or more per parent. The issue is the access pattern, not simply the presence of lazy loading.
- Capture query counts and timing for the actual endpoint, including serialization and view rendering that may access relationships after the initial query.
- Identify which associations are traversed and which fields the response truly needs, checking result size and pagination requirements.
- Choose an appropriate fetch plan, batch fetch, projection, or separate bounded bulk query rather than making every association eager globally.
- Verify query count, returned row cardinality, memory, and response correctness with representative parent counts and relationship sizes.
flowchart TD A["Measure endpoint database queries"] B["Locate repeated relationship loads"] C["Define required response data"] D["Choose bounded fetch strategy"] E{"Pagination and row counts correct?"} F["Keep measured improvement"] G["Revise fetch plan"] A --> B B --> C C --> D D --> E E -->|yes| F E -->|no| GTrade-offs:
- Fetching more relationships in one query can create a large Cartesian expansion or interfere with collection pagination.
- Globally eager loading can replace one inefficiency with unnecessary work on endpoints that do not need the relationship.
Example
An endpoint loads 20 orders and then reads each order’s customer separately.
- Query logging shows one order query followed by 20 customer lookups.
- Choose a fetch plan or projection containing the required customer fields.
- Confirm the resulting response still contains exactly the intended order page.
- Compare query count, memory, and latency as the page size changes.
Q62.How do optimistic and pessimistic locking handle concurrent updates?
Optimistic locking detects a conflicting change before accepting an update. Pessimistic locking coordinates access by holding an appropriate database lock while the protected operation executes.
Optimistic Pessimistic Checks a version or equivalent condition Acquires and holds a lock Failed attempts need resolution Waiting and possible deadlocks When to use which:
- Use optimistic locking when conflicts are relatively uncommon and the application can ask the caller to reload, merge, or retry safely after a version mismatch.
- Use pessimistic locking when coordinating a short critical operation is more appropriate, with consistent lock ordering and bounded transaction duration.
- Use a conditional atomic update when a single statement can express the invariant directly, avoiding an unnecessary read-modify-write gap.
Neither approach removes the need to define the invariant or handle failure. A version field must be checked and updated correctly to provide optimistic protection.
Example
Two users load a resource at version 4.
- The first update succeeds only where the stored version is 4, then writes version 5.
- The second update still expects version 4 and affects no matching row.
- Treat that outcome as a concurrency conflict rather than silently overwriting the newer value.
- Reload or resolve the change according to the user workflow.
Q63.How would you prevent two checkout requests from buying the last available item?
I would enforce the stock invariant atomically in the authoritative database. A frontend check or an earlier unlocked read cannot protect against another request changing stock before the write.
- Define whether checkout reserves or immediately consumes stock, along with reservation expiry, cancellation, and the consistency required for order creation.
- Use a conditional decrement, suitable row locking, or a version-checked update so success depends on enough stock at the actual write boundary.
- Check the affected-row or conflict result and commit related local order state consistently, preserving idempotency across request retries.
- Handle reservation release and external payment outcomes through a recoverable workflow, then test simultaneous requests against the last unit.
flowchart TD A["Receive checkout request"] B["Apply atomic stock condition"] C{"Stock change accepted?"} D["Commit reservation and local order state"] E["Return unavailable or conflict"] F["Coordinate payment and final outcome"] G["Release reservation when required"] A --> B B --> C C -->|yes| D C -->|no| E D --> F F -->|failure| GTrade-offs:
- Holding a database lock during a remote payment call can create long contention and should not be the default coordination strategy.
- Reservations reduce overselling risk but add expiry and recovery behavior that must be tested explicitly.
Example
Only one unit remains, and two requests arrive together.
- Both attempt an atomic decrement conditioned on stock being at least one.
- Only one successfully changes stock from one to zero.
- The other sees no accepted stock update and receives the defined unavailable outcome.
- A retry of the successful request reuses its existing order or reservation identity.
Q64.What is the difference between flushing a persistence context and committing a transaction?
Flushing synchronizes eligible pending persistence changes with the database. Committing completes the transaction successfully, subject to its configured durability and consistency guarantees.
Flush Commit Sends pending changes to the database Finalizes the transaction The transaction is not necessarily complete The transaction is complete, if the commit succeeds When to use which:
- Use flush when synchronization must occur before a particular check or operation, understanding that SQL errors or constraints may surface at that point.
- Use commit as the boundary for reporting a completed durable database outcome, because a successful flush can still be followed by rollback or commit failure.
- Understand automatic flush behavior before queries and at transaction completion so performance and error timing are not surprising.
Flushed changes can still be uncommitted and invisible to other transactions under their isolation rules. Repeated unnecessary flushes can reduce batching efficiency, while delaying every check until commit can make error attribution harder.
Q65.How would you change a database schema without breaking a running application?
I would use compatible expansion, migration, and contraction steps. During a rolling release, old and new application versions may access the database at the same time.
- Assess consumers, data volume, locking behavior, and the compatibility window, then introduce additive schema changes that both deployed versions can tolerate.
- Deploy code that understands the new structure, coordinating reads and writes during the transition and validating any temporary dual-write behavior.
- Backfill existing data in controlled batches, verify completeness and business consistency, and switch authoritative reads only after the required data is ready.
- Remove obsolete fields or constraints only after old consumers are retired and rollback requirements have been satisfied, with monitored execution and recovery plans.
flowchart TD A["Expand schema compatibly"] B["Deploy compatible application"] C["Backfill and reconcile data"] D{"All required consumers migrated?"} E["Switch authoritative use"] F["Retire obsolete schema safely"] G["Continue compatibility period"] A --> B B --> C C --> D D -->|yes| E D -->|no| G E --> FTrade-offs:
- Temporary compatibility code increases complexity but allows independent application rollout and data migration.
- Even an additive change can take locks or consume significant resources, so verify the actual database operation at the expected scale.
Example
A single display-name field is being replaced by a structured profile representation.
- Add the new fields without immediately deleting the old field.
- Deploy compatible reads and writes, then backfill according to an agreed mapping.
- Validate records that cannot be transformed automatically.
- Retire the old field only after all active consumers have migrated.
Q66.Why do semantic HTML and accessibility matter in a full stack application?
Semantic HTML communicates the purpose and relationships of interface elements to browsers and assistive technology. Accessibility also improves keyboard use, error recovery, and the clarity of ordinary interaction.
- Native elements: use actual buttons, links, headings, lists, and form controls when they match the task, instead of rebuilding their behavior on generic containers.
- Names and relationships: associate labels and descriptions with controls, give meaningful accessible names, and preserve a logical document and focus order.
- Interaction: support keyboard access, visible focus, appropriate error feedback, and predictable focus movement after navigation, dialogs, and asynchronous updates.
- Verification: combine automated checks with manual keyboard and assistive-technology testing for representative user journeys, including loading and failure states.
ARIA can express additional semantics where necessary, but adding a role does not implement the keyboard behavior of the corresponding native control.
Q67.How does the CSS box model affect an element’s rendered size?
The box model separates content, padding, border, and margin. The box-sizing property determines which parts are included in a declared width or height.
- Content-box: a declared width normally sizes the content area, so horizontal padding and borders add to the outer border-box width.
- Border-box: the declared width includes content, padding, and borders, which often makes component sizing easier to reason about.
- Margin: margins sit outside the border box and affect layout spacing; they are not included merely because border-box sizing is enabled.
- Layout context: minimum and maximum sizes, flex or grid constraints, intrinsic content, and overflow can influence the final dimensions, so inspect computed layout when the result differs.
Treat width calculations as one part of responsive design. Long content, zoom, and translated text can expose assumptions that worked for a single fixed sample.
Example
An element has width 200px, padding 10px on each side, and a 2px border on each side.
- With content-box, the border-box width is
200 + 10 + 10 + 2 + 2 = 224px. - With border-box, the border-box width remains
200px. - Its content width is then
200 - 20 - 4 = 176px. - Any margins remain outside these border-box measurements.
Q68.How do the CSS cascade and selector specificity resolve conflicting styles?
The cascade determines which applicable declaration wins. Specificity is one comparison inside that process, so the most specific selector does not override every declaration regardless of origin, importance, or layer.
- Cascade context: evaluate applicable origins, importance, and cascade-layer rules before comparing competing selector specificity within the relevant context.
- Specificity: compare selector categories in their defined order, with identifiers outweighing class-like selectors and those outweighing type selectors; it is not ordinary decimal addition.
- Tie-breaking: when earlier criteria are equal, additional cascade rules and ultimately source order decide the winning declaration.
- Maintainability: prefer predictable layers and modest selectors, using special specificity behavior such as zero-specificity selectors deliberately rather than escalating with repeated important declarations.
Inspect the browser’s matched and computed styles to see which rule won and why. A declaration can also lose because it is invalid or not applicable, regardless of specificity.
Q69.When should you use Flexbox rather than CSS Grid?
Flexbox primarily arranges items along one dimension at a time. Grid defines coordinated rows and columns, making it useful when both dimensions should align as one layout.
Flexbox Grid Distributes content along one axis Aligns tracks in two dimensions Used for navigation or control rows Used for page sections or structured galleries When to use which:
- Use Flexbox when items should distribute, wrap, or align primarily along one axis and their content contributes strongly to the layout behavior.
- Use Grid when row and column relationships should be planned together, including layouts with explicit tracks or responsive repeated columns.
- Combine them when a page grid contains components whose internal controls are best arranged with a flexible row or column.
Neither layout guarantees responsiveness by itself. Test intrinsic minimum sizes, long text, narrow viewports, zoom, and reading order so visual rearrangement does not undermine keyboard or semantic navigation.
Q70.How do var, let, and const differ in JavaScript?
The declarations differ in scope, initialization behavior, and reassignment rules. const prevents reassignment of a binding, not mutation of the object that the binding references.
var let const Function or relevant global scope Block scope Block scope Reassignment allowed Reassignment allowed No reassignment after initialization When to use which:
- Use const when the binding should not be reassigned, making stable references visible while still managing any mutable object contents deliberately.
- Use let when reassignment is part of the algorithm or interaction state, keeping the variable in the narrowest useful block.
- Understand var when reading existing code, especially function scoping and its different behavior when closures capture loop variables.
let and const are subject to the temporal dead zone before initialization, while var bindings are initialized differently. Saying that let and const are simply not hoisted misses the important distinction between binding creation and usable initialization.
Q71.What is a JavaScript closure, and how would you use one to retain private state?
A closure lets a function access bindings from the lexical environment where it was created, including after the outer function has returned. It retains access to bindings rather than simply copying their original values.
- State retention: an inner function can read and update variables belonging to its enclosing scope, making a small controlled interface for state possible.
- Independent instances: separate calls to a factory create separate environments, so one instance’s counter or settings need not affect another’s.
- Common uses: closures support callbacks, function factories, event handlers, and partial application without requiring globally shared variables.
- Lifecycle: retained closures can also retain large objects, so remove unused listeners and references when their owners are no longer needed.
Distinguish lexical scope from this binding: an ordinary function and an arrow function can both close over variables while handling this differently.
Example
Each counter factory call creates an independent retained count.
function createCounter() { let count = 0; return function next() { count += 1; return count; }; } const first = createCounter(); const second = createCounter(); console.log(first()); console.log(first()); console.log(second()); // Expected output: // 1 // 2 // 1Q72.In what order do synchronous code, promise callbacks, and timers execute?
In a typical browser event-loop turn, the current synchronous work finishes before queued promise reactions run. An eligible timer callback runs as a later task, after the relevant microtask checkpoint.
- Call stack: synchronous statements run to completion within their current job, so an already-resolved promise does not interrupt the statement that registered its callback.
- Microtasks: promise reactions are queued for microtask processing, which drains pending microtasks at defined checkpoints before the next ordinary task proceeds.
- Timers: a zero delay is a minimum scheduling request rather than a guarantee of immediate execution or an exact wall-clock deadline.
- Responsiveness: long synchronous work blocks interaction on the same thread, and repeatedly scheduling microtasks can also delay later tasks and rendering opportunities.
Host environments have additional scheduling details, so reason from the specific APIs involved rather than treating every asynchronous callback as one identical queue.
Example
The promise reaction runs after the synchronous statements and before the zero-delay timer in this example.
console.log('start'); setTimeout(() => console.log('timer'), 0); Promise.resolve().then(() => console.log('promise')); console.log('end'); // Expected output: // start // end // promise // timerQ73.How do promises and async/await support asynchronous JavaScript error handling?
A promise represents the eventual fulfillment or rejection of an operation. async/await provides structured syntax for composing promise-based work without making the waiting operation synchronous for the whole event loop.
- Return behavior: an async function returns a promise, and an exception thrown from its execution becomes a rejected result that callers must handle.
- Awaiting: await suspends that function’s continuation until the awaited result settles, while other eligible event-loop work can continue.
- Failure propagation: use try/catch around awaited operations when that scope can recover, and preserve or rethrow failures instead of converting every rejection into an unexplained empty result.
- Concurrency: start independent operations before awaiting them together when useful; sequential awaits can serialize work unintentionally, and aggregate rejection does not automatically cancel sibling operations.
Always distinguish transport completion from application success. Some APIs resolve a promise for an unsuccessful protocol response, requiring an explicit status check.
Q74.How does this differ between ordinary JavaScript functions and arrow functions?
An ordinary function’s this value generally depends on how it is called. An arrow function captures this lexically from its enclosing context and does not create its own this binding.
Ordinary function Arrow function this depends on the call site this is inherited lexically Has its own arguments object Has no own arguments object Can be a constructor, depending on its form Cannot be used as a constructor When to use which:
- Use an ordinary function when an operation should receive a receiver through a method call, explicit binding, or an API that intentionally supplies this.
- Use an arrow when a callback should keep the surrounding this value and a new dynamic receiver would be confusing or unnecessary.
- When passing a method as a callback, decide whether to bind its receiver, wrap the call, or avoid relying on this rather than assuming the original object is retained automatically.
A detached ordinary method call can lose its intended receiver. Also, call, apply, or bind cannot replace an arrow function’s lexical this in the same way they can bind an ordinary function.
Q75.What is event delegation, and when is it useful?
Event delegation places a handler on an ancestor and uses event propagation to handle interactions from relevant descendants. It can simplify dynamic lists without installing a separate handler on every child.
- Propagation: determine whether the event bubbles or requires a suitable capture-phase strategy, rather than assuming every event reaches the ancestor in the same way.
- Targeting: distinguish the original event target from the element holding the listener, and locate the intended actionable descendant when a nested icon or label was clicked.
- Scope: confirm the matched element belongs to the intended container and validate the action before changing application state.
- Lifecycle: remove the ancestor listener when its owner is destroyed, and preserve native keyboard and accessibility behavior instead of handling only pointer clicks.
Delegation reduces listener management for some interfaces, but it does not eliminate event ordering, propagation control, or ownership concerns in nested components.
Q76.What is the difference between debouncing and throttling?
Debouncing waits for a suitable quiet period before running work, while throttling limits how often work may run during continued activity. Exact leading and trailing behavior should be part of the chosen contract.
Debounce Throttle Collapses a burst into one later call Limits how often calls run during activity Example: search after typing pauses Example: periodic scroll or movement updates When to use which:
- Use debouncing when intermediate values are usually unnecessary and the final value after a burst is the useful input, such as a delayed search request.
- Use throttling when the interface needs continuing updates during sustained activity, while limiting the work performed per unit time.
- Define cancellation, leading-edge calls, trailing-edge calls, and maximum waiting behavior explicitly so navigation or component cleanup does not leave stale work behind.
Neither technique guarantees the order in which network responses arrive. Server-side quotas and duplicate protection also require their own enforcement rather than relying on client-side event timing.
Q77.How would you implement a trailing-edge debounce that preserves arguments and this?
I would retain one timer, cancel it on each new call, and schedule the latest arguments and receiver after the delay. A regular wrapper function lets the caller supply this normally.
- Replacement: clearing the previous timer ensures a synchronous burst schedules only one eventual invocation using its latest arguments.
- Receiver: capture the wrapper’s this for that invocation and use apply when calling the wrapped function, rather than losing its method context.
- Lifecycle: expose cancellation so an owning component can discard pending work when it is removed or the operation becomes irrelevant.
- Contract: validate the delay and document that this simple wrapper does not return the eventual asynchronous result or implement leading-edge execution.
Timers are scheduling requests, so the actual invocation may occur later than the configured delay when the event loop is busy.
Example
This example keeps only the final search value and preserves the receiver’s prefix.
function debounce(fn, delay) { if (typeof fn !== 'function' || !Number.isFinite(delay) || delay < 0) { throw new TypeError('function and nonnegative finite delay required'); } let timer; function debounced(...args) { clearTimeout(timer); const receiver = this; timer = setTimeout(() => { timer = undefined; fn.apply(receiver, args); }, delay); } debounced.cancel = () => { clearTimeout(timer); timer = undefined; }; return debounced; } const search = { prefix: 'query:', publish: debounce(function (value) { console.log(this.prefix + value); }, 20) }; search.publish('j'); search.publish('ja'); search.publish('java'); // Expected output after the delay: query:javaQ78.How would you prevent an older API response from overwriting newer frontend state?
I would associate each request with its current owner and input version, then apply results only while they are still relevant. Request cancellation helps reduce work but should not be the only correctness check.
- Create a new request identity whenever the relevant input changes, and cancel an earlier fetch with an abort signal when appropriate.
- Check the HTTP response status explicitly before parsing the expected payload, because a fetch can resolve successfully for an HTTP error response.
- Before changing results, loading state, or error state, verify that the request still matches the latest input and that its owning view remains active.
- Handle cancellation separately from meaningful failures and clean up outstanding work when the user navigates away or the component is replaced.
flowchart TD A["Record latest input and request identity"] B["Cancel obsolete request when possible"] C["Fetch and validate response"] D{"Response successful?"} E["Parse expected data"] F{"Request still current?"} G["Update current view state"] H["Ignore obsolete outcome"] I["Handle current error safely"] A --> B B --> C C --> D D -->|yes| E D -->|no| I E --> F F -->|yes| G F -->|no| HTrade-offs:
- Cancellation can save resources, but a version check protects state even when a response or later processing has already completed.
- A global loading flag is insufficient when several independent operations have different owners or lifetimes.
Example
The user searches for one term and immediately enters a newer term.
- Start the newer request with a higher request identity.
- The newer response arrives first and updates the view.
- The older response arrives later but fails the current-identity check.
- It changes neither the displayed results nor the newer request’s error or loading state.
Q79.How do component inputs, local state, shared state, and server state differ?
These categories describe ownership and authority, which determine where data should live and how changes should propagate. Duplicating the same authoritative value in several places creates synchronization problems.
Inputs or props Local state Shared client state Server state Owned by the parent or caller Owned by one component or view Owned by several coordinated views Owned by a remote authoritative system Configures a component Holds temporary interaction details Drives cross-view client behavior Holds loaded resources and mutations When to use which:
- Keep transient interaction state close to the component that owns it, such as an expanded panel or unfinished input that no other view needs.
- Lift or share client state when multiple components require coordinated changes, while preserving one clear authoritative owner for each value.
- Treat server state as remote data with loading, caching, freshness, error, and mutation behavior rather than assuming it is permanent local truth.
Derive values when practical instead of storing redundant copies. An optimistic client update still needs reconciliation with the server’s accepted result and conflict behavior.
Q80.Why do stable keys matter when rendering dynamic component lists?
Keys help a component renderer associate an existing rendered item and its state with the correct logical item across updates. They describe identity, not merely a convenient position in an array.
- Stable identity: use a persistent item identifier that remains associated with the same business item during insertion, deletion, filtering, and reordering.
- Index limitations: positional keys can attach input state or focus to a different item after list changes; they are safer only when the position truly remains the identity.
- Random keys: generating a new key on every render can force unnecessary replacement and discard local state instead of preserving the existing item.
- Uniqueness scope: meet the renderer’s sibling-level uniqueness requirement and understand its behavior when an item deliberately changes identity.
A key is not an authorization control or a substitute for a database uniqueness constraint. Test list changes while users are editing or focusing an item, not only static initial rendering.