What batch processing, memory limits, lazy loading, and downloads taught me about frontend development.
An image compressor looks like a small frontend project: select a file, adjust the quality, press a button, and save the result.
Add a folder of high-resolution photos, a phone with limited memory, and a download that needs to contain every successful result, and the engineering problem gets more interesting.
I’m building CompressFly, a browser-based toolkit for everyday file tasks. Its image compression and conversion tools process images locally in the browser. Working on those tools has made me think more carefully about what happens between selecting a file and actually finishing the job.
The useful lessons reach beyond image tools. They apply to document editors, media utilities, import screens, and other web applications that do substantial work on a user’s device.
1. Local processing changes where the constraints live
For image compression and format conversion, keeping the work in the browser means the source images do not need to be uploaded to a processing server.
That is useful, but it also makes the user’s device part of the processing environment. Available memory, other open tabs, and the size of the selected images all affect what the application can comfortably handle.
The website still needs to load its application resources. Local image processing is a statement about where the image work happens; it is not a promise that the entire site works offline.
That distinction belongs in the interface. Someone deciding whether to select personal photos should be able to understand the data flow without knowing how the application is built.
My takeaway: explain the boundary of a privacy claim as carefully as the benefit. A specific explanation is more useful than a broad “everything is secure” badge.
2. File size is a poor estimate of working memory
A photo that occupies a few megabytes on disk can require much more memory once decoded.
For a simple example, consider a 6,000 × 4,000 image represented as four 8-bit color channels. That pixel buffer alone contains:
*6,000 × 4,000 × 4 = 96,000,000 bytes, or about 92 MiB.
*
This is an illustrative calculation, not a measurement of CompressFly. It excludes the original file, output data, previews, and any temporary allocations. MDN’s ImageData documentation describes the underlying pixel representation, including RGBA channel order.
That helps explain why “the total upload size looks small” is not enough to judge a batch.
It also changes the questions worth asking during development. How many images need to be decoded at once? Does a small preview need the full-resolution source? How long does temporary data remain reachable after an operation ends?
Those questions matter even when the interface looks simple. A sensible memory budget can determine whether a tool remains usable on the device someone already has.
3. A batch needs scheduling, not just a loop
Suppose someone selects 40 product photos. Starting all 40 operations immediately may look efficient, but it can also create a burst of decoding, temporary buffers, and result updates.
A more useful way to think about the problem is a queue with a bounded amount of active work. When one task finishes, another can start. One slow image should not prevent unrelated work from continuing.
There is no universally correct concurrency setting. Image dimensions, processing cost, and device resources all matter. A processor count alone does not tell you how much image data a browser can safely keep in memory.
The interface also needs to distinguish files waiting for their turn from files actively being processed. Otherwise, a perfectly ordinary queue can look like a stalled application.
Batch processing changes the meaning of success, too. If one image fails, users still need to know which other images completed and what they can save. A single error message for the entire folder hides information they need to finish the task.
That is why I think of batch support as a workflow feature. Accepting multiple files is only the beginning.
4. Lazy loading needs a plan for the first click
A file tool can accumulate dependencies for previews, encoders, archives, and downloads. Loading every optional feature at the start makes a visitor pay for work they may never use.
JavaScript’s dynamic import provides a way to load modules asynchronously when needed. The product question is when that need becomes likely.
For a preview or download feature, an early interaction can be a useful hint. Pointer interaction and keyboard focus matter alongside desktop hover; a phone user should have an equally reliable path.
Preloading remains an optimization. The actual action must still wait for the required feature to be ready and handle a loading failure. Several interactions should share the same loading work rather than create competing initialization paths.
A failed background load also needs a deliberate retry policy. Otherwise, an optimization can quietly leave a feature unusable for the rest of the session.
This is the part of lazy loading I find easy to underestimate: splitting the bundle is only one piece of making a deferred feature feel dependable.
5. Stopping work and ignoring old results are separate problems
Asynchronous work can outlive the interface that started it.
Imagine a user begins processing, stops, and starts again with different settings. An earlier operation may finish after the new one has begun. If its result is still accepted, the screen can show an output that belongs to the previous request.
The same issue appears in search suggestions and image editors. The operation completed successfully, but its result is no longer relevant.
Cancellation helps, provided the underlying work cooperates with the signal. The browser’s AbortController API supports signaling cancellation; it does not automatically make arbitrary computation stop.
The application also needs a clear rule for whether a result still belongs to the current task. That rule matters for errors and cleanup as well as successful results. An old failure should not overwrite a newer success message.
Thinking through these cases early makes the interface easier to reason about. A button labeled “Stop” creates an expectation about everything that happens afterward.
6. The download is part of the processing workflow
Converting 40 images and requiring 40 separate downloads leaves a large part of the original repetitive task in place.
For supported batch workflows, collecting completed files in a ZIP makes the output easier to save together. But archiving introduces its own decisions: which results belong in the archive, how filenames are handled, and what happens if the preferred download method is unavailable.
There is also a distinction between packaging and compression. A ZIP is useful for grouping images, but already-compressed image files may not become much smaller inside it.
Memory matters again at this stage. Building an entire archive in memory and writing output progressively have different resource requirements. Browser support and failure handling need to be considered along with the preferred path. Progressive output also does not eliminate memory already occupied by retained results.
The CompressFly image compressor brings these concerns into one visible workflow: select images, choose settings, review the results, and download completed files individually or together.
That final step deserves the same attention as the processing button. Users came for files they can use elsewhere.
What I would focus on when building another file tool
I would start with a complete, modest workflow: one file, an understandable result, and a working download. Then I would introduce mixed batches, delayed dependencies, failures, and repeated actions.
The questions I would keep nearby are practical:
Can the user tell what is waiting, working, finished, or failed?
Is the amount of active work appropriate for the available resources?
Can an old operation change a newer result?
Does a deferred feature still work when preloading never happened?
Can the user recover completed files when part of a batch fails?
Building a browser-based image tool has given me a better appreciation for these ordinary details. The visible operation may be “compress this image,” but the development work stretches from the first file selection to the last saved result.
I’m continuing that work in CompressFly. For a concrete example, you can try its HEIC to JPG converter and follow the path from a batch of source photos to downloadable copies.



Top comments (0)