N or P
Contents Glossary
31

Bin packing — fit the items into the fewest containers

Bin packing is the workhorse hard problem. When it shows up, the approximation is usually good enough.

Bin packing: given items with various sizes and bins with fixed capacity, pack everything into the fewest bins. Business uses include packing virtual machines onto physical servers, packing files onto storage volumes, packing cargo into containers, packing ad creatives into broadcast time slots, packing software jobs onto compute clusters. Strongly NP-complete. But the approximation picture is excellent. First-Fit-Decreasing (FFD) — sort items by decreasing size, place each in the first bin that fits — uses at most 11/9 of the optimal bin count. On real workloads, the result is typically within a few percent of optimal. For business problems where the approximation gap matters, model as integer programming and call HiGHS. Most operational bin packing in production uses FFD as the workhorse and reserves exact solvers for offline planning.

  1. 01
    First-Fit-Decreasing — sort items by descending size, place each in the first bin that fits. At most 11/9 of optimal, usually within a few percent on real workloads. Twenty lines of Rust replaces a quarter of bespoke search code.
    fn first_fit_decreasing(sizes: &[u32], capacity: u32) -> Vec<Vec<u32>> {
        let mut items = sizes.to_vec();
        items.sort_unstable_by(|a, b| b.cmp(a));
        let mut bins: Vec<Vec<u32>> = Vec::new();
        for size in items {
            let used: Vec<u32> = bins.iter().map(|b| b.iter().sum()).collect();
            match used.iter().position(|&u| u + size <= capacity) {
                Some(i) => bins[i].push(size),
                None => bins.push(vec![size]),
            }
        }
        bins
    }
  2. 02
    Vec::new() and vec![] both produce an empty Vec — for the empty case they are equivalent (the vec! macro literally expands to Vec::new() when there are no elements). Neither allocates heap memory until the first push. The choice between them is stylistic. Convention: reach for Vec::new() when you are declaring an empty vec that will be filled later (the constructor name is visible — "I am starting empty"); reach for the vec! macro when you have initial contents (vec![1, 2, 3]) or the repeat form (vec![0; n]). Two practical differences exist. Vec::new() is a const fn, so it works in const contexts where the macro does not. And with an explicit type annotation on the binding, = Vec::new() reads slightly more clearly than = vec![] — the constructor name reinforces what the line is doing. A third constructor worth knowing is Vec::with_capacity(n) — empty (.len() == 0) but pre-allocated room for n elements, useful when you know roughly how many items will be pushed and want to skip the doubling reallocations a growing vec performs.
    // Three ways to create a Vec, each useful in different cases.
    
    // 1. Empty, no allocation yet.  Either form works; vec![] is the macro
    //    and Vec::new() is the constructor — equivalent for the empty case.
    let v: Vec<i32> = Vec::new();              // constructor form
    let v: Vec<i32> = vec![];                   // macro form
    
    // 2. With initial contents — the macro is the obvious choice.
    let v: Vec<i32> = vec![1, 2, 3];            // literal form
    let v: Vec<i32> = vec![0; 100];             // repeat form: 100 zeros
    
    // 3. Empty but with pre-allocated capacity — neither of the above.
    let v: Vec<i32> = Vec::with_capacity(100);  // empty (len == 0)
                                                 // room for 100 pushes
                                                 // without any realloc
    
    
    // Why Vec::new() on page 31 instead of vec![]?
    //
    // Convention.  "empty + will be filled later" reads cleanly with the
    // constructor; the presence of "Vec::new()" makes the empty start
    // visually distinct from "vec![..items..]" with content.  Both compile
    // to identical code.
    
    let mut bins: Vec<Vec<u32>> = Vec::new();
    for size in items {
        // ... bins.push(vec![size]) or bins[i].push(size) inside the loop ...
    }
    
    
    // Practical differences worth knowing:
    //
    //   Vec::new()              const fn — usable in const contexts.
    //                           static EMPTY: Vec<i32> = Vec::new();  ← compiles
    //
    //   vec![]                  macro — not usable in const contexts.
    //                           static EMPTY: Vec<i32> = vec![];      ← error
    //
    //   Vec::with_capacity(n)   empty, but heap-allocates room for n elements.
    //                           Use when you know roughly how big the vec will
    //                           grow, to avoid the doubling reallocations.
    
    
    // All three start at .len() == 0 — the difference is how much heap
    // memory the vec is sitting on:
    Vec::<i32>::new().capacity();              // 0
    vec![1, 2, 3].capacity();                   // typically 3 (or more)
    Vec::<i32>::with_capacity(100).capacity(); // 100
Johnson, D. (1973). Garey, M., Johnson, D. (1979). Source →
In plain terms

Bin packing is one of the most useful "hard" problems in business operations. It shows up everywhere you have fixed-capacity containers to fill: virtual machines on physical servers, files on disks, cargo in shipping containers, ads in television breaks, compute jobs on cluster nodes, parts in shipping pallets.

The problem is np-complete" data-term="strongly NP-complete">strongly NP-complete — even when all the sizes are bounded by polynomially in the number of items, no polynomial-time exact algorithm is known. But the approximation picture is unusually good.

The simplest heuristic that works is First-Fit: process items in arrival order, put each one in the first bin that has room, open a new bin only when none does. First-Fit uses at most 70% more bins than optimal.

First-Fit-Decreasing is the same algorithm with items sorted in decreasing size first. This is the workhorse approximation. It uses at most 11/9 of the optimal bin count — about 22% worse than optimal in the worst case. On typical operational workloads, the result is within a few percent of optimal, often within one bin.

For business problems where the few-percent gap matters — long-term capacity planning, billing where overprovisioning is expensive — model as integer programming. Variables for "item i in bin j," a constraint that every bin's total size is under capacity, an objective to minimize the number of used bins. Hand to HiGHS via lp" data-term="good_lp">good_lp. Modern MIP solvers handle hundreds of items in seconds for exact answers.

The pattern that works in production: use FFD as the default for real-time decisions (server packing on incoming requests, real-time logistics). Use exact integer programming for offline planning (quarterly capacity, route design). The combination gives you good decisions fast and great decisions overnight.

For planning estimates: bin packing requests should be sized as days, not weeks, because the algorithm is well-understood and the libraries handle the work. The engineering effort is in defining sizes correctly (what is the "capacity"? what is the "size"?), not in the packing.

The category is "hard but well-tooled." Use the tools.

← 30 Find a subset that sums right
31/39
32 When the variables must be whole numbers →