Game Performance
Updated: Jun 28, 2026
Copy for LLM
Performance is one of the most important factors for a successful Instant Game. Many of your players will be on mobile devices with limited processing power, memory, and network bandwidth. A game that loads slowly, runs at a low frame rate, or crashes due to memory issues will lose players quickly.
This guide covers everything you need to know to build a fast, efficient Instant Game.
Bundle size optimization
Your game is delivered as a bundle (a zip file uploaded to Facebook or hosted externally). The size of this bundle directly affects how quickly players can start playing.
Size limits and targets
| Metric | Value | Notes |
|---|---|---|
Maximum total bundle size | 200 MB | Hard limit enforced by the platform |
Recommended initial bundle | Under 5 MB | What players download before the game starts |
Ideal initial bundle | Under 3 MB | Provides the best start-up experience |
Target time-to-play | Under 5 seconds | On a 3G connection |
The distinction between “initial bundle” and “total bundle” is important. Your initial bundle is the zip file you upload, which must contain everything needed to show the first frame of your game. Additional assets can be loaded progressively after the game starts (see Progressive Loading below).
How to reduce bundle size
- Audit your bundle: Unzip your build and examine every file. Look for files that should not be there (development tools, source maps, documentation, unused assets).
- Minify JavaScript: Use a minifier like Terser, UglifyJS, or your engine’s built-in minification. Minification can reduce JS file size by 50-70%.
npx terser game.js -o game.min.js --compress --mangle - Minify CSS: Use a CSS minifier like cssnano or clean-css.
- Remove unused code: Use tree shaking (available in bundlers like Webpack, Rollup, and esbuild) to eliminate JavaScript code that is never called.
- Compress images: See Asset Optimization below.
- Use audio compression: See Audio Optimization below.
- Defer non-essential assets: Only include assets needed for the first screen in your initial bundle. Load everything else after the game starts.
Asset optimization
Assets (images, audio, fonts, data files) typically make up the majority of your bundle size. Optimizing them yields the biggest improvements.
Image optimization
Images are usually the largest assets in a game. Use these techniques to reduce their size.
Choose the right format
| Format | Best For | Transparency | Compression |
|---|---|---|---|
WebP | Most game art | Yes | Lossy and lossless, 25-35% smaller than PNG |
PNG | UI elements, pixel art needing exact colors | Yes | Lossless |
JPEG | Photographic backgrounds, large images without transparency | No | Lossy, very good compression |
SVG | Simple icons, UI elements that scale | Yes | Vector (tiny file sizes for simple shapes) |
AVIF | Next-gen alternative to WebP (check browser support) | Yes | Even better compression than WebP |
Recommendation: Use WebP as your default image format. It is supported by all modern browsers and provides excellent compression with good quality. Fall back to PNG or JPEG only when WebP is not suitable.
Image compression tips
- Resize images to their display size: If an image is displayed at 100x100 pixels, do not include a 1000x1000 source file. Resize it to 100x100 (or 200x200 for high-DPI displays).
- Use lossy compression when possible: For most game art, a quality setting of 75-85% produces images that look great but are significantly smaller.
- Use tools like these for batch compression:
- TinyPNG — PNG and WebP compression
- Squoosh — Compare formats and quality settings
- ImageOptim — Lossless optimization
- Remove metadata: Strip EXIF data and other metadata from images.
Sprite atlases (sprite sheets)
Combining multiple small images into a single sprite atlas reduces the number of HTTP requests and improves rendering performance.
- Pack sprites into atlases using tools like TexturePacker, ShoeBox, or your game engine’s built-in atlas tools.
- A single 2048x2048 atlas is better than 100 individual 64x64 sprites.
- Keep atlas dimensions as powers of 2 (256, 512, 1024, 2048) for optimal GPU memory usage.
Texture compression
For WebGL games, use GPU-compressed texture formats to reduce video memory usage and improve rendering performance:
- ETC1/ETC2 — Supported on most mobile devices.
- ASTC — Higher quality, supported on newer devices.
- Basis Universal — A “supercompressed” format that can be transcoded to the best format for the current device.
Note: GPU texture compression reduces memory and GPU load but does not always reduce download size. Combine it with standard file compression.
Audio optimization
Audio files are often among the largest assets in a game. Optimize them with these techniques.
| Format | Use Case | Typical Compression |
|---|---|---|
MP3 | Music, long audio | Good compression, widely supported |
OGG Vorbis | Music, long audio | Better compression than MP3 |
AAC | Music, long audio | Good compression, required for iOS |
WAV | Avoid in production | Uncompressed, very large |
Recommendations:
- Never ship WAV files. Convert them to MP3 or OGG.
- Use mono audio for sound effects. Stereo doubles the file size and is usually unnecessary for short sound effects.
- Reduce sample rate: 22,050 Hz is sufficient for most sound effects. Use 44,100 Hz only for music where quality is critical.
- Reduce bitrate: 128 kbps is sufficient for music in most games. 64 kbps works for sound effects.
- Trim silence: Remove any silence at the beginning or end of audio files.
- Use audio sprites: Combine short sound effects into a single audio file and play specific segments, similar to sprite atlases for images.
Font optimization
- Use system fonts when possible: System fonts (like
-apple-system, sans-serif) do not add any size to your bundle. - Subset custom fonts: If you must use a custom font, use a tool like Font Squirrel or glyphhanger to include only the characters you actually use.
- Use WOFF2 format: WOFF2 is the most compressed web font format and is supported by all modern browsers.
Progressive and lazy loading
Instead of putting everything in your initial bundle, load assets progressively as the player needs them.
Strategy
- Initial bundle: Include only what is needed to start the game — the core engine, the first screen’s assets, and the loading UI. Target under 5 MB.
- During loading screen: While the SDK loading screen is showing, begin fetching assets for the first level or main menu.
- After game start: Load subsequent levels, optional features, and bonus content in the background as the player plays.
Implementation
Use the
FBInstant.setLoadingProgress() function to report real loading progress during the initial load:FBInstant.initializeAsync().then(function() { var totalAssets = 10; var loadedAssets = 0; function onAssetLoaded() { loadedAssets++; var progress = Math.floor((loadedAssets / totalAssets) * 100); FBInstant.setLoadingProgress(progress); if (loadedAssets === totalAssets) { FBInstant.startGameAsync().then(function() { startGame(); }); } } // Load each asset and call onAssetLoaded when done loadImage('hero.png', onAssetLoaded); loadImage('background.png', onAssetLoaded); loadAudio('music.mp3', onAssetLoaded); // ... more assets });
For post-start loading, use standard web APIs (
fetch, XMLHttpRequest, Image, Audio) or your engine’s asset loader to fetch additional assets from your hosted bundle or a CDN.Hosted assets
You can host additional assets externally (on your own server or a CDN) instead of including them in the zip bundle. This allows you to:
- Keep the initial bundle small.
- Update assets without re-uploading the entire bundle.
- Load assets on demand.
Important: Make sure your external server supports HTTPS (required) and has proper CORS headers.
JavaScript optimization
Minification and bundling
Always minify your JavaScript for production builds:
- Use a bundler like Webpack, Rollup, esbuild, or Parcel to combine your modules into a single file and apply minification.
- Enable tree shaking to remove unused code.
- Generate source maps separately (do not include them in your upload bundle) so you can debug issues if needed.
Avoid memory leaks
Memory leaks cause your game to consume more and more memory over time, eventually leading to crashes — especially on mobile devices with limited memory.
Common causes of memory leaks in games:
| Cause | Solution |
|---|---|
Event listeners not removed | Always remove listeners when objects are destroyed. |
References to destroyed objects | Null out references when objects are no longer needed. |
Growing arrays/lists | Limit collection sizes; remove old entries. |
Closures capturing large scopes | Be mindful of what variables closures capture. |
Uncleared intervals/timeouts | Clear setInterval and setTimeout when no longer needed. |
Avoid frequent garbage collection
JavaScript’s garbage collector pauses your game briefly each time it runs. Frequent GC pauses cause visible stuttering.
Strategies to reduce GC pressure:
- Object pooling: Pre-allocate objects (bullets, particles, and enemies) and reuse them instead of creating new ones. See Object Pooling below.
- Avoid creating objects in the game loop: Do not use
new,{},[], or string concatenation inside yourupdate()function. - Reuse vector/point objects: Instead of creating
{x: 0, y: 0}every frame, create one and update its properties. - Use typed arrays: For numerical data (positions, velocities),
Float32Arrayis more memory-efficient than regular arrays.
// Bad: Creates a new object every frame function update() { var velocity = { x: 1, y: 0 }; // New object every frame! player.x += velocity.x; player.y += velocity.y; } // Good: Reuse an existing object var velocity = { x: 1, y: 0 }; function update() { player.x += velocity.x; player.y += velocity.y; }
Frame rate optimization
Your game should target 60 frames per second (fps) for smooth gameplay. On lower-end devices, 30 fps is an acceptable fallback. Anything below 30 fps will feel choppy to players.
Use requestAnimationFrame
Always use
requestAnimationFrame for your game loop. It synchronizes with the display refresh rate, is more power-efficient than setInterval or setTimeout, and is automatically paused when the tab is hidden.function gameLoop(timestamp) { update(timestamp); render(); requestAnimationFrame(gameLoop); } requestAnimationFrame(gameLoop);
Use delta time
Never assume a fixed frame rate. Use the time elapsed since the last frame (delta time) to make movement and animations independent of frame rate:
var lastTime = 0; function gameLoop(timestamp) { var deltaTime = (timestamp - lastTime) / 1000; // Convert to seconds lastTime = timestamp; // Move 100 pixels per second, regardless of frame rate player.x += 100 * deltaTime; render(); requestAnimationFrame(gameLoop); }
Avoid layout thrashing
When manipulating the DOM (if your game uses HTML elements), avoid interleaving reads and writes. Reading layout properties (like
offsetWidth, getBoundingClientRect) forces the browser to recalculate layout. If you do this repeatedly in a loop, it causes “layout thrashing” and severe performance drops.// Bad: Layout thrashing elements.forEach(function(el) { var width = el.offsetWidth; // Read (forces layout) el.style.width = (width + 10) + 'px'; // Write (invalidates layout) }); // Good: Batch reads, then batch writes var widths = elements.map(function(el) { return el.offsetWidth; // All reads first }); elements.forEach(function(el, i) { el.style.width = (widths[i] + 10) + 'px'; // All writes after });
Canvas and WebGL tips
- Minimize draw calls: Batch similar objects together. Use sprite atlases so multiple sprites can be drawn in a single draw call.
- Reduce canvas resolution on low-end devices: Rendering at 75% resolution and scaling up can double frame rates with minimal visual difference.
var scale = isLowEndDevice() ? 0.75 : 1.0; canvas.width = window.innerWidth * scale; canvas.height = window.innerHeight * scale; canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px';
- Avoid overdraw: Do not draw objects that are completely hidden behind other objects.
- Use
willReadFrequentlyfor 2D canvas if you callgetImageDataoften:var ctx = canvas.getContext('2d', { willReadFrequently: true });
Memory management
Mobile devices have limited memory. If your game uses too much, the operating system will kill it.
Memory budget
| Device Tier | Available Memory | Your Target |
|---|---|---|
Low-end Android | 512 MB - 1 GB total | Under 100 MB for your game |
Mid-range mobile | 2 - 3 GB total | Under 150 MB for your game |
High-end mobile / Desktop | 4+ GB total | Under 200 MB for your game |
Remember, the browser and operating system also use memory. Your game does not get the full device memory.
Object pooling
Object pooling is one of the most effective techniques for both memory and performance in games. Instead of creating and destroying objects (bullets, particles, enemies), you pre-create a pool and reuse them.
function createPool(factory, size) { var pool = []; for (var i = 0; i < size; i++) { var obj = factory(); obj.active = false; pool.push(obj); } return { get: function() { for (var i = 0; i < pool.length; i++) { if (!pool[i].active) { pool[i].active = true; return pool[i]; } } // Pool exhausted -- optionally grow it var obj = factory(); obj.active = true; pool.push(obj); return obj; }, release: function(obj) { obj.active = false; }, getActiveObjects: function() { return pool.filter(function(obj) { return obj.active; }); } }; } // Usage var bulletPool = createPool(function() { return { x: 0, y: 0, vx: 0, vy: 0, active: false }; }, 50); function fireBullet(x, y) { var bullet = bulletPool.get(); bullet.x = x; bullet.y = y; bullet.vx = 0; bullet.vy = -500; }
Texture memory
Images and textures consume significant GPU memory. A 2048x2048 RGBA texture uses 16 MB of GPU memory, regardless of the compressed file size on disk.
- Unload textures for screens or levels that are no longer visible.
- Use smaller textures when possible. A 1024x1024 texture uses 4x less memory than 2048x2048.
- Use texture compression formats (ETC2, ASTC) to reduce GPU memory usage by 4-8x.
Network optimization
Players may be on slow or unreliable networks. Optimize network usage to handle this gracefully.
- Cache aggressively: Use the browser’s cache (via proper HTTP headers on your hosted assets) to avoid re-downloading assets on subsequent visits.
- Compress network payloads: Use gzip or Brotli compression for API responses and data files.
- Minimize API calls: Batch multiple data requests into single calls when possible.
- Handle offline gracefully: Detect network failures and show meaningful error messages instead of crashing.
- Use exponential backoff for retries: When a network request fails, wait increasing intervals before retrying (1s, 2s, 4s, 8s, and so on) to avoid overwhelming the server.
function fetchWithRetry(url, retries, delay) { return fetch(url).catch(function(err) { if (retries > 0) { return new Promise(function(resolve) { setTimeout(function() { resolve(fetchWithRetry(url, retries - 1, delay * 2)); }, delay); }); } throw err; }); } // Usage: try up to 3 times, starting with a 1-second delay fetchWithRetry('https://example.com/data.json', 3, 1000);
Testing on low-end devices
A game that runs well on your development machine might perform poorly on the devices your players actually use. Testing on low-end devices is essential.
Minimum device requirements
Instant Games must run on:
| Platform | Minimum Version | Rendering |
|---|---|---|
Android | 5.0 (Lollipop) and above | Android System WebView (Chromium-based) |
iOS | 10.0 and above | WKWebView (Safari-based) |
Desktop | Modern Chrome, Firefox, Safari, Edge | Full browser |
Testing recommendations
- Test on a real low-end Android device: Borrow or purchase an inexpensive Android phone (under $100). This will reveal performance issues you cannot see on your development machine.
- Use Chrome DevTools throttling: If you do not have a low-end device, use Chrome DevTools to simulate slower conditions.
- CPU throttling: Open DevTools > Performance > CPU dropdown > select 4x slowdown or 6x slowdown.
- Network throttling: Open DevTools > Network > Throttling dropdown > select Slow 3G or Fast 3G.
- Monitor performance metrics:
- Frame rate: Use
performance.now()or Chrome DevTools’ FPS meter to measure frame rate. Target 60 fps; accept 30 fps minimum. - Memory usage: Use Chrome DevTools > Memory to take heap snapshots and identify memory growth.
- Load time: Measure time from page load to
startGameAsync()completion.
- Frame rate: Use
- Test on iOS: iOS WebViews behave differently from Android. Audio autoplay policies, memory limits, and rendering behavior vary. Test on a real iPhone or iPad if possible.
Chrome DevTools performance profiling
The Performance tab in Chrome DevTools is your most powerful tool for finding performance bottlenecks:
- Open your game in Chrome.
- Open DevTools (F12 or Ctrl+Shift+I).
- Go to the Performance tab.
- Click the record button and play your game for a few seconds.
- Stop recording and analyze the flame chart.
- Look for:
- Long frames (frames that take more than 16.67ms, which means you dropped below 60 fps).
- Frequent garbage collection (GC) events.
- Heavy scripting in the main thread.
- Layout recalculations caused by DOM manipulation.
Using FBInstant.setLoadingProgress() properly
The loading progress bar is the first thing players see. Misusing it creates a poor first impression.
Do
- Report progress accurately: If you are loading 20 assets, update progress after each one loads.
- Start at 0 and end at 100: The progress should go from 0 to 100 smoothly.
- Only call
startGameAsync()when truly ready: Do not start the game before essential assets are loaded. - Make progress updates feel smooth: Avoid long pauses at any percentage.
Do not
- Do not jump from 0 to 100 instantly: This makes the loading bar useless and can look broken.
- Do not report fake progress: Do not use a timer to slowly increment progress if nothing is actually loading. If the real loading finishes at 40% fake progress, the player will see a jarring jump.
- Do not get stuck at 99%: If your progress reaches 99% and then takes 10 more seconds, players will think the game is frozen. Design your loading pipeline so the final steps are fast.
- Do not call
setLoadingProgress()afterstartGameAsync(): The loading bar is gone at that point. It has no effect.
Example: Accurate progress reporting
FBInstant.initializeAsync().then(function() { var assetManifest = [ { type: 'image', url: 'sprites/hero.png' }, { type: 'image', url: 'sprites/enemies.png' }, { type: 'image', url: 'sprites/background.png' }, { type: 'image', url: 'sprites/ui.png' }, { type: 'audio', url: 'audio/music.mp3' }, { type: 'audio', url: 'audio/sfx.mp3' }, { type: 'data', url: 'data/levels.json' }, { type: 'data', url: 'data/config.json' } ]; var loaded = 0; var total = assetManifest.length; function updateProgress() { loaded++; var percent = Math.floor((loaded / total) * 100); FBInstant.setLoadingProgress(percent); if (loaded === total) { FBInstant.startGameAsync().then(function() { initializeGame(); }); } } assetManifest.forEach(function(asset) { loadAsset(asset).then(updateProgress); }); });
Performance checklist
Use this checklist before submitting your game:
Bundle size
- Initial bundle is under 5 MB
- All JavaScript is minified
- Unused code has been removed (tree shaking)
- No source maps included in the upload bundle
- No development-only files (tests, docs, READMEs)
Images
- Images are in WebP format (or PNG/JPEG where appropriate)
- Images are resized to their display dimensions
- Sprites are packed into atlases
- No unnecessarily large images
Audio
- No WAV files in the bundle
- Sound effects are mono
- Audio bitrate is appropriate (128 kbps max for music, 64 kbps for SFX)
- Silence is trimmed from audio files
Runtime performance
- Game targets 60 fps (or 30 fps minimum)
- Game loop uses
requestAnimationFrame - Movement and animation use delta time
- No objects created inside the game loop (use object pooling)
- No layout thrashing (if using DOM elements)
Memory
- No memory leaks (verified with DevTools heap snapshots over several minutes of play)
- Event listeners are cleaned up when objects are destroyed
- Unused textures are unloaded when no longer needed
- Object pooling is used for frequently created/destroyed objects
Loading
FBInstant.setLoadingProgress()reports accurate progress- Loading progress increases smoothly (no long pauses)
FBInstant.startGameAsync()is called only when the game is ready- Non-essential assets are loaded progressively after game start
Device testing
- Tested on a low-end Android device (or with CPU throttling)
- Tested on iOS
- Tested on desktop
- Tested on slow network (3G throttling)
- No crashes after extended play sessions (5+ minutes)