Context-free languages are closed under union, concatenation, and Kleene star, but notably NOT under intersection or complement. The union of two CFLs is proved CFL by combining their grammars with a new start variable; concatenation and star are similar. The failure of closure under intersection is shown by the counterexample {aⁿbⁿcⁿ} = {aⁿbⁿc* } ∩ {a*bⁿcⁿ}, each of which is CFL. However, the intersection of a CFL with a regular language is always CFL (proved by a product construction of PDA and DFA).
From your study of regular languages, you know that closure properties tell you what operations you can perform on languages in a class and still stay within that class. Regular languages have a clean story: they are closed under union, intersection, complement, concatenation, and Kleene star. Context-free languages have a more nuanced profile, and understanding exactly where CFLs are closed — and where they are not — is essential for proving languages non-context-free and for building complex grammars from simpler ones.
The good news first: CFLs are closed under union, concatenation, and Kleene star. The proofs are constructive and elegant. For union, given grammars G₁ with start variable S₁ and G₂ with start variable S₂, you create a new grammar with a fresh start variable S and add the rules S → S₁ | S₂. A derivation from S picks one grammar or the other, producing a string from L(G₁) ∪ L(G₂). Concatenation works similarly: S → S₁S₂ forces a derivation that produces a string from L(G₁) followed by a string from L(G₂). For Kleene star: S → SS₁ | ε generates zero or more repetitions. These constructions mirror the ones you saw for regular languages, and they work because context-free grammars can freely combine in these ways without losing their context-free character.
The critical failure points are intersection and complement. CFLs are *not* closed under either operation. The canonical counterexample for intersection uses two languages that are individually context-free: L₁ = {aⁿbⁿcᵐ | n, m ≥ 0} and L₂ = {aᵐbⁿcⁿ | n, m ≥ 0}. A PDA can recognize each one by using its stack to match one pair of symbols. But their intersection is L₁ ∩ L₂ = {aⁿbⁿcⁿ | n ≥ 0}, which requires matching three groups simultaneously — something no single stack can do. Since intersection fails, complement must fail too: if CFLs were closed under complement, you could express intersection via De Morgan's laws (A ∩ B = complement of (complement(A) ∪ complement(B))), and union closure would then force intersection closure — a contradiction.
There is one important partial result that you should keep in your toolkit: the intersection of a CFL with a regular language is always a CFL. The proof builds a product machine that runs a PDA and a DFA in parallel — the DFA handles the regular constraint while the PDA handles the context-free structure. This result is extremely useful in practice: when you need to show that a language like "all valid arithmetic expressions that contain at least one multiplication" is context-free, you can express it as a CFL (valid expressions) intersected with a regular language (strings containing ×), and the closure property guarantees the result stays context-free.