Frontend Interview: Design a Progressive-Enhancement File Import Flow
Question
A desktop web app imports large files. Users may click to choose a file, drag files onto a drop zone, or use a browser that supports showOpenFilePicker(). Design the complete frontend flow, including feature detection, fallback, cancellation, progress, accessibility, privacy, and retry. Explain which capabilities are enhancements rather than baseline dependencies.
Reference answer
Use one input model: a File or a readable file handle. The UI should receive only candidate files, source, size, type, and state. The baseline is a visible, labelled <input type="file"> because it is broadly available; drag-and-drop is another way to produce the same model. If window.showOpenFilePicker exists in a secure context, use it only from a user-click handler as an enhancement. If the picker fails, is cancelled, or permission is denied, keep the input path available instead of treating the exception as lack of support.
Model the flow as idle → selected → validating → reading → uploading → complete, with cancelled and failed branches. Validate extension, declared MIME, actual size limits, and business format; client checks provide fast feedback, while the server must validate again. Read large files as streams or slices, cancel reads and uploads with AbortController, retry only replayable chunks, and show completed bytes plus an estimate.
Names, paths, and contents are user data. Show only necessary metadata and keep content out of logs; revoke preview object URLs with URL.revokeObjectURL(). The drop zone needs a keyboard and ordinary-button path, with focus, errors, and progress exposed through semantic elements and a live region. Dragging must not be the only operation. Tell the user when data will be uploaded, then clean up local references and unfinished work after cancellation.
Key difficulties
showOpenFilePicker() requires transient user activation, so it cannot be called from a timer, page load, or a delayed background task. It returns handles, which do not imply permanent read permission. Feature detection does not guarantee permission. accept on an input is a hint, not a security check, and File.type may be empty or client-supplied. Drop handlers must prevent default navigation and handle directories, multiple files, and very large lists.
Common mistakes
- Treating the File System Access API as a baseline dependency.
- Trusting an extension or MIME value as file security validation.
- Hiding the input without preserving a label, keyboard path, and screen-reader path.
- Reading an entire large file into memory before uploading.
- Showing “permission denied” for cancellation, format errors, and network failures alike.
Trade-off analysis
The input is the dependable baseline with the widest reach, but it lacks handle-level operations. The picker can reduce repeated selection and expose richer handle operations, yet depends on browser support, HTTPS, user activation, and permission. Drag-and-drop improves desktop throughput but needs a keyboard alternative. Client validation shortens feedback time; server validation is the security boundary. Chunking and resumability add state complexity but reduce the cost of retrying large transfers.
Runnable example
async function chooseFiles(input) {
if (window.showOpenFilePicker && window.isSecureContext) {
try {
const handles = await window.showOpenFilePicker({ multiple: true });
return Promise.all(handles.map((handle) => handle.getFile()));
} catch (error) {
if (error?.name !== "AbortError") throw error;
}
}
return Array.from(input.files ?? []);
}Call this from a user-click handler. Production code should also constrain type, count, and size and map failures to user-facing states. A drop handler can convert event.dataTransfer.files to the same array and reuse the validation and upload state machine.
Interviewer follow-ups
Why not call showOpenFilePicker() during page load? The API requires transient user activation, so delayed or background calls can be rejected. Does accept stop malware? No. It is a picker hint; security checks belong on the server. Why keep a fallback? Browser support and permission can vary, so the baseline must work with the widely available input.
Follow-up responses
How do you test fallback paths?
Test an unsupported browser, a non-secure context, cancellation, and denied permission separately; assert that the input path still completes an import.
How do you prevent memory exhaustion?
Use File.slice() or streams, cap concurrency, upload chunks, and abort the controller when the user cancels.
How do you make the flow accessible?
Keep a real label and button, make the drop zone focusable, let the keyboard trigger the input, and associate errors and progress with text and a live region.
Which metrics show that it works?
Track success, cancellation, validation failure, eventual completion after retry, time to first progress, and fallback rates by browser without recording file contents.