The identities that turn binomial sums into closed forms, divisibility facts (Lucas, Kummer, parity), and algorithms for sums of binomial coefficients that have no closed form: prefix sums of a row (single and many offline queries) and sums over a residue class. Binomials mod a prime come from Comb / mint in Modular Arithmetic.

IdentityName / use
Pascal
symmetry
absorption
trinomial revision (“subset of a subset”)
, row sums
, weighted row sums
hockey stick (column sums)
hockey stick (diagonal)
Vandermonde
Vandermonde with
for ; alternating partial sums
; upper negation, negative binomial series
shallow diagonals give Fibonacci
multinomial theorem
binomial inversion
, central binomial size
upper bound used in analyses
DivisibilityStatement
Lucas over the base- digits: Factorials mod p
parity is odd iff (every bit of is set in ); row has odd entries: Sierpiński’s triangle
Kummerthe exponent of in is the number of carries when adding and in base
Legendre ( = digit sum)
for the freshman’s dream
int kummerCarries(ll n, ll k, ll p) {                      // v_p(C(n, k)) = carries adding k and n - k in base p
    int carries = 0; ll a = k, b = n - k, c = 0;
    while (a > 0 || b > 0 || c > 0) { ll d = a % p + b % p + c; c = d >= p; carries += (int)c; a /= p; b /= p; }
    return carries;
}
 
mint binomialPrefix(ll n, ll k) {                          // sum_{i=0}^{k} C(n, i), n < modulus, O(k)
    if (k < 0) return 0;
    k = min(k, n); mint res = 1, cur = 1;
    for (ll i = 1; i <= k; i++) { cur = cur * mint(n - i + 1) / mint(i); res += cur; }
    return res;
}
 
// Many queries f(n, k) = sum_{i<=k} C(n, i) offline with Mo's algorithm: O((N + q) sqrt N) moves of O(1).
vector<mint> binomialPrefixQueries(const vector<pair<int, int>>& qs, const Comb& cb) {   // cb must cover N + 1
    int q = sz(qs); vector<mint> res(q); if (!q) return res;
    int N = 1; for (auto [n, k] : qs) N = max({N, n + 1, k + 1});
    int B = max(1, (int)(N / sqrt((double)q)));
    vector<int> order(q); iota(all(order), 0);
    sort(all(order), [&](int a, int b) {
        int ba = qs[a].first / B, bb = qs[b].first / B;
        if (ba != bb) return ba < bb;
        return (ba & 1) ? qs[a].second > qs[b].second : qs[a].second < qs[b].second;
    });
    mint inv2 = mint(1) / mint(2); int n = 0, k = 0; mint f = 1;   // f(0, 0) = 1
    for (int id : order) {
        auto [tn, tk] = qs[id];
        while (n < tn) f = mint(2) * f - cb.C(n, k), n++;
        while (n > tn) f = (f + cb.C(n - 1, k)) * inv2, n--;
        while (k < tk) f += cb.C(n, k + 1), k++;
        while (k > tk) f -= cb.C(n, k), k--;
        res[id] = f;
    }
    return res;
}
 
vector<mint> binomialResidueSums(ll n, int m) {            // res[r] = sum_{i = r (mod m)} C(n, i), any n <= 1e18, O(m^2 log n)
    auto mul = [&](const vector<mint>& a, const vector<mint>& b) {
        vector<mint> c(m, 0);
        for (int i = 0; i < m; i++) if (a[i] != mint(0)) for (int j = 0; j < m; j++) c[(i + j) % m] += a[i] * b[j];
        return c;
    };
    vector<mint> res(m, 0), base(m, 0); res[0] = 1; base[0] += 1; base[1 % m] += 1;   // 1 + x
    for (; n; n >>= 1, base = mul(base, base)) if (n & 1) res = mul(res, base);
    return res;
}

For with the modulus use Lucas; for large in the residue sums use NTT cyclic convolution instead of the product. Sums of a row prefix have no closed form, which is why the Mo walk (or the residue-class trick) is needed; for a single query with small the loop suffices.