“Build an avatar upload” is a classic frontend machine-coding question that looks simple until you implement it. The hard parts: image rotation from EXIF, the cropping interaction, processing the cropped image at the right resolution, and the upload error handling. This guide covers what makes a good answer.
Clarify scope
- Square crop only or any aspect ratio?
- Single avatar or multiple sizes (thumbnail, banner, etc.)?
- Drag-and-drop and click-to-select?
- Mobile camera capture in scope?
- Direct upload to S3 or via your server?
The picker
- Hidden
<input type="file" accept="image/*"> - Click trigger; or drag-drop zone with
onDragOver/onDrop - Multiple file types: PNG, JPG, WebP, HEIC (iOS), GIF (animated)
- Size limit and validation (e.g., 10 MB max)
- “Take a photo” on mobile via
capture="user"attribute
The EXIF rotation trap
iOS photos often have an EXIF orientation flag rather than rotated pixels. Naive rendering shows them sideways:
- Read EXIF data with a library (exifr, exif-parser)
- Apply rotation in canvas before display
- Modern browsers respect EXIF for
<img>elements but not always for canvas; do not rely on it
The HEIC challenge
- iOS photos are often HEIC, which Safari renders but Chrome / Firefox do not
- Convert to JPEG / WebP server-side or with heic2any in the browser
- Handle gracefully; do not show “broken image” if user uploads HEIC
The crop interaction
The standard pattern:
- Image visible with a crop overlay (square or aspect-ratio constrained)
- Drag the crop box to reposition
- Pinch / wheel to zoom the underlying image
- Apply / Cancel buttons
Use a library (react-easy-crop, react-image-crop) or implement with canvas + pointer events. For interview, a small custom one demonstrates skill; for production, the libraries are battle-tested.
Processing the crop
- Draw the original image to a hidden canvas
- Translate / scale based on crop selection
- Read back as a Blob using
canvas.toBlob - Specify output format (image/webp for size, image/jpeg for compatibility)
- Specify quality (0.8 is a reasonable default)
Output sizing
- Most products use 256×256 or 512×512 for avatars
- Generate multiple sizes server-side for responsive display
- If client-side, use
OffscreenCanvasfor non-blocking processing
Upload
Direct-to-S3 (signed URL)
- Server generates a signed PUT URL
- Client uploads directly to S3 using the URL
- Server records the resulting object key
- No server bandwidth cost; faster for users
Upload through server
- Client POSTs Blob to your API
- Server validates, processes, stores
- Higher control; higher bandwidth cost
- Required if you do server-side image transformation
Progress and feedback
- Show upload progress percentage (XMLHttpRequest or fetch with upload progress)
- Optimistic UI: show preview immediately, replace with server URL on success
- Error handling: surface clear errors; allow retry
- Cancellation: AbortController if the user navigates away
Accessibility
- File input is keyboard-accessible by default; do not break it
- Crop interaction needs keyboard alternatives — arrow keys to move, +/- to zoom
- Live region announces status: “Image selected, ready to crop”, “Uploading 50%”
Security and validation
- Validate MIME type client-side and server-side
- Reject obvious junk (text files renamed to .jpg)
- Server-side: re-encode the image to strip metadata and exploits
- Limit dimensions (10000×10000 → reject, prevents DoS)
Privacy concerns
- EXIF data may include GPS coordinates of where the photo was taken
- Strip EXIF before storing user-uploaded images
- Document this in your privacy policy
Mobile considerations
- iOS:
capture="user"launches front camera;capture="environment"for back - Pinch zoom in the cropper; touch-friendly drag
- Resize image before upload on mobile (data costs)
Edge cases interviewers love
- User uploads a 50 MB photo — show error or auto-downscale before crop
- User uploads animated GIF — preserve animation in some products, freeze in others
- EXIF rotation — actually applies the rotation
- Network drops mid-upload — retry with backoff
- User navigates away mid-upload — abort, do not leave a half-uploaded blob
What separates senior from staff
Senior implementations handle the upload + crop. Staff implementations address EXIF rotation, HEIC conversion, accessible crop interactions, and the multi-size server pipeline. Principal candidates discuss the security implications (re-encoding, EXIF stripping) and the on-demand image-resize pipeline (CDN with image transforms).
Frequently Asked Questions
Library options for production?
react-easy-crop, react-image-crop, Cropper.js (vanilla). For React Native, react-native-image-crop-picker. For full image-CDN handling, Cloudinary or imgix.
Should I generate avatar sizes client-side?
Generally no — server-side image transforms (sharp, ImageMagick) are more reliable. Client-side is appropriate when you want to upload only the cropped result and avoid bandwidth.
What about animated avatars?
GIFs and short videos are increasingly common. Decide product policy: allow or freeze to first frame. For mid-tier products, freezing is the norm.