Chapter 2: Van der Waals forces and Hydrophobic Interactions in Ligand-protein Interactions#

1. Introduction#

Van der Waals forces are weak, short-range attractive interactions that occur when atoms from a drug molecule and its target protein come into close proximity (typically 3-4 Å). While individually weak (0.5-1 kcal/mol per contact), these forces are numerous across a binding interface—a typical drug-protein complex may have 50-100 van der Waals contacts contributing 25-50% of total binding energy. Understanding these forces is essential because they reward shape complementarity: drugs that fit snugly into binding pockets maximize favorable contacts while minimizing steric clashes.


2. Key Concepts and Definitions#

  • Van der Waals forces: Weak, short-range attractive forces between atoms arising from transient charge fluctuations (London dispersion) and permanent dipole interactions, effective at 3-4 Å distances.

  • London dispersion forces: Instantaneous dipole-induced dipole attractions that occur between all atoms; stronger for larger, more polarizable atoms like sulfur, chlorine, and aromatic carbons.

  • Hydrophobic effect The entropy-driven burial of nonpolar surfaces that works synergistically with van der Waals forces to favor compact binding geometries.


3. Main Content#

3.1 Types of van der Waals Interactions in Drug Binding#

London dispersion forces dominate drug-protein interactions, occurring between all atom pairs regardless of polarity. These arise from correlated electron fluctuations creating transient dipoles. Larger atoms (Cl, Br, S, aromatic carbons) are more polarizable and form stronger dispersion interactions—explaining why many drugs incorporate halogens or aromatic rings.

Dipole-dipole interactions occur between permanent dipoles in polar groups. The C=O···C=O interaction between amide groups (~1 kcal/mol) is common at protein-ligand interfaces. These are directional and require proper geometric alignment, making them valuable for binding specificity.

Dipole-induced dipole forces arise when polar groups polarize nearby nonpolar atoms. A carbonyl oxygen can induce a dipole in an adjacent aromatic ring, creating an additional attractive force that stabilizes specific binding orientations.

Non-Covalent Interactions Summary#

Interaction Type

Typical Strength

Distance Range

Key Properties

London dispersion

0.5 - 1.0 kcal/mol

3.5 - 4.0 Å

All atoms; stronger for larger atoms

Dipole-dipole

0.5 - 2.0 kcal/mol

3.0 - 4.0 Å

Directional; requires alignment

π-π stacking

2 - 4 kcal/mol

3.5 - 4.0 Å

Face-to-face or T-shaped geometries

CH-π interaction

1 - 2 kcal/mol

3.0 - 4.0 Å

Alkyl/aromatic CH to π system

Hide code cell source

import numpy as np
from rdkit import Chem
from rdkit.Chem import AllChem
from IPython.display import HTML
import json
from rdkit import RDLogger

# Disable RDKit warnings for cleaner output
RDLogger.DisableLog('rdApp.*')

def create_cl2_aligned():
    """Create a Cl2 molecule explicitly aligned on the X-axis."""
    mol = Chem.MolFromSmiles('ClCl')
    mol = Chem.AddHs(mol)
    AllChem.EmbedMolecule(mol, randomSeed=42)
    
    conf = mol.GetConformer()
    conf.SetAtomPosition(0, [0.0, 0.0, 0.0])  
    conf.SetAtomPosition(1, [1.99, 0.0, 0.0]) 
    
    return mol

def position_cl2_pair(distance, stage):
    """Position two Cl2 molecules and return both the SDF block and the list of charges."""
    mol1 = create_cl2_aligned()
    mol2 = create_cl2_aligned()
    
    mol_center = 1.0 
    
    conf1 = mol1.GetConformer()
    for i in range(mol1.GetNumAtoms()):
        pos = conf1.GetAtomPosition(i)
        conf1.SetAtomPosition(i, [pos.x - (distance/2) - mol_center, pos.y, pos.z])
    
    conf2 = mol2.GetConformer()
    for i in range(mol2.GetNumAtoms()):
        pos = conf2.GetAtomPosition(i)
        conf2.SetAtomPosition(i, [pos.x + (distance/2) - mol_center, pos.y, pos.z])

    q1_left, q1_right = 0.0, 0.0
    q2_left, q2_right = 0.0, 0.0
    
    # Logic for appearing dipoles
    if stage >= 3:
        q1_left = -0.3
        q1_right = 0.3
    
    if stage >= 5:
        q2_left = -0.3
        q2_right = 0.3
    
    mol1.GetAtomWithIdx(0).SetDoubleProp("_GasteigerCharge", q1_left)
    mol1.GetAtomWithIdx(1).SetDoubleProp("_GasteigerCharge", q1_right)
    
    mol2.GetAtomWithIdx(0).SetDoubleProp("_GasteigerCharge", q2_left)
    mol2.GetAtomWithIdx(1).SetDoubleProp("_GasteigerCharge", q2_right)
    
    combined = Chem.CombineMols(mol1, mol2)
    frame_charges = [atom.GetDoubleProp("_GasteigerCharge") for atom in combined.GetAtoms()]

    return Chem.MolToMolBlock(combined), frame_charges

def generate_dispersion_trajectory():
    trajectory_data = []
    distances = []
    stage_descriptions = []
    stages = []
    all_charges = [] 
    stage_explanations = []
    
    num_frames = 41
    
    for i in range(num_frames):
        distance = 10.0 - (6.5 * i / (num_frames - 1))
        
        # Stage logic
        if distance > 8.0:
            stage = 0
            desc = "Molecules far apart"
            explanation = {"title": "No Interaction", "what": "Two neutral Cl₂ molecules are separated.", "why": "Too far apart.", "result": "No force."}
        elif distance > 7.0:
            stage = 1
            desc = "Approaching"
            explanation = {"title": "Initial Approach", "what": "Moving closer.", "why": "Thermal motion.", "result": "Clouds sense each other."}
        elif distance > 6.0:
            stage = 2
            desc = "Random fluctuations"
            explanation = {"title": "Electron Fluctuations", "what": "Electrons in motion.", "why": "Quantum uncertainty.", "result": "Asymmetric distribution."}
        elif distance > 5.5:
            stage = 3
            desc = "Instantaneous dipole"
            explanation = {"title": "Instantaneous Dipole Forms", "what": "Left molecule develops δ- / δ+.", "why": "Random fluctuation.", "result": "Field extends to neighbor."}
        elif distance > 5.0:
            stage = 4
            desc = "Field propagates"
            explanation = {"title": "Electric Field Propagates", "what": "Field reaches neighbor.", "why": "Field decreases with distance.", "result": "Neighbor begins responding."}
        elif distance > 4.5:
            stage = 5
            desc = "Neighbor responds"
            explanation = {"title": "Induced Dipole Forms", "what": "Right molecule develops δ- / δ+.", "why": "Pushed/pulled by field.", "result": "Dipoles align (+ to -)."}
        elif distance > 4.0:
            stage = 6
            desc = "Correlated dipoles"
            explanation = {"title": "Correlated Motion", "what": "Dipoles synchronized.", "why": "Mutual reinforcement.", "result": "Net attractive force."}
        elif distance > 3.7:
            stage = 7
            desc = "Attractive interaction"
            explanation = {"title": "Attractive Force Dominates", "what": "Opposites attract.", "why": "Coulombic attraction.", "result": "Molecules pulled together."}
        else:
            stage = 8
            desc = "Maximum attraction"
            explanation = {"title": "Optimal Distance", "what": "Maximum attraction.", "why": "Balance with repulsion.", "result": "Stable separation."}
        
        scene_sdf, frame_charges = position_cl2_pair(distance, stage)
        trajectory_data.append(scene_sdf)
        distances.append(distance)
        stage_descriptions.append(desc)
        stages.append(stage)
        all_charges.append(frame_charges)
        stage_explanations.append(explanation)
    
    return trajectory_data, distances, stage_descriptions, stages, all_charges, stage_explanations

def create_dispersion_viewer(width=900):
    trajectory_data, distances, descriptions, stages, all_charges, explanations = generate_dispersion_trajectory()
    full_trajectory = "\n$$$$\n".join(trajectory_data) + "\n$$$$\n"
    
    ids = {k: f"{k}_{np.random.randint(1e5, 9e5)}" for k in ['viewer', 'slider', 'distance', 'status', 'explanation']}
    
    # Build the explanation HTML separately to avoid f-string complexity
    explanation_template = '''
                    <div style="font-size: 11px; font-weight: bold;">STAGE ''' + '${stages[currentFrame] + 1}' + '''</div>
                    <div style="font-size: 16px; font-weight: bold; color: #2196F3;">''' + '${exp.title}' + '''</div>
                    <p><b>What:</b> ''' + '${exp.what}' + '''</p>
                    <p><b>Why:</b> ''' + '${exp.why}' + '''</p>
                    <p><b>Result:</b> ''' + '${exp.result}' + '''</p>
                '''
    
    html = f"""
    <div style="font-family: Arial, sans-serif; max-width: {width}px; margin: 20px auto;">
        <script src="https://3Dmol.org/build/3Dmol-min.js"></script>
        <div style="border: 3px solid #2196F3; border-radius: 10px; padding: 15px; background: white; margin-bottom: 20px;">
            <div style="text-align: center; margin-bottom: 15px;">
                <h3 style="color: #2196F3; margin: 0; font-size: 16px;">London Dispersion Forces</h3>
            </div>
            <div style="display: flex; gap: 20px; align-items: flex-start;">
                <div style="flex: 1.2; position: relative;">
                    <div id="{ids['viewer']}" style="width: 100%; height: 350px; position: relative;"></div>
                    <div style="position: absolute; top: 10px; right: 10px; background: rgba(255,255,255,0.9); padding: 8px; border-radius: 4px; font-size: 11px; border: 1px solid #ddd;">
                        <div><span style="color: blue;">■</span> δ+ (Positive)</div>
                        <div><span style="color: red;">■</span> δ− (Negative)</div>
                    </div>
                    <div style="text-align: center; margin-top: 10px;">
                        <span style="font-size: 16px; font-weight: bold; color: #2196F3;" id="{ids['distance']}"></span>
                    </div>
                </div>
                <div style="flex: 1;">
                    <div id="{ids['explanation']}" style="background: #e3f2fd; border-radius: 8px; padding: 15px; border: 2px solid #2196F3; min-height: 300px;"></div>
                    <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-top: 15px;">
                        <input type="range" id="{ids['slider']}" min="0" max="40" value="0" style="width: 100%;">
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <script>
    (function() {{
        setTimeout(function() {{
            const element = document.getElementById('{ids['viewer']}');
            const trajectory = {json.dumps(full_trajectory)};
            const chargeData = {json.dumps(all_charges)};
            const distances = {json.dumps(distances)};
            const stages = {json.dumps(stages)};
            const explanations = {json.dumps(explanations)};
            
            const viewer = $3Dmol.createViewer(element, {{ backgroundColor: '#f0f8ff' }});
            viewer.addModelsAsFrames(trajectory, 'sdf');
            viewer.setStyle({{}}, {{stick: {{radius: 0.15}}, sphere: {{scale: 0.3}}}});
            viewer.zoomTo();
            
            function updateVisuals() {{
                const currentFrame = parseInt(document.getElementById('{ids['slider']}').value);
                viewer.setFrame(currentFrame);
                
                const frameCharges = chargeData[currentFrame];
                const atoms = viewer.getModel().selectedAtoms({{}});
                
                viewer.removeAllLabels();
                
                for(let i=0; i < atoms.length; i++) {{
                    const charge = frameCharges[i];
                    atoms[i].properties._GasteigerCharge = charge;
                    
                    if (Math.abs(charge) > 0.01) {{
                        let labelText = charge > 0 ? "δ+" : "δ-";
                        let labelColor = charge > 0 ? "#0000FF" : "#FF0000";
                        
                        viewer.addLabel(labelText, {{
                            position: {{x: atoms[i].x, y: atoms[i].y + 0.8, z: atoms[i].z}},
                            backgroundColor: 'white',
                            backgroundOpacity: 0.5,
                            fontColor: labelColor,
                            fontSize: 14,
                            alignment: 'center'
                        }});
                    }}
                }}
                
                viewer.removeAllSurfaces();
                viewer.addSurface($3Dmol.SurfaceType.VDW, {{
                    opacity: 0.7,
                    colorscheme: {{ gradient: 'rwb', min: -0.3, max: 0.3, prop: '_GasteigerCharge' }}
                }});
                
                viewer.render();
                document.getElementById('{ids['distance']}').innerText = "Distance: " + distances[currentFrame].toFixed(2) + " Å";
                
                const exp = explanations[currentFrame];
                document.getElementById('{ids['explanation']}').innerHTML = `{explanation_template}`;
            }}
            
            document.getElementById('{ids['slider']}').oninput = updateVisuals;
            updateVisuals();
        }}, 100);
    }})();
    </script>b
    """
    return HTML(html)

# Run the visualization
create_dispersion_viewer()

London Dispersion Forces

δ+ (Positive)
δ− (Negative)
b

Hide code cell source

import numpy as np
from rdkit import Chem
from rdkit.Chem import AllChem
from IPython.display import HTML
import json
from rdkit import RDLogger

# Disable RDKit warnings for cleaner output
RDLogger.DisableLog('rdApp.*')

def create_hcl_flipped():
    """Create HCl with Cl on the left (-) and H on the right (+)."""
    mol = Chem.MolFromSmiles('[H]Cl')
    mol = Chem.AddHs(mol) # Ensure H is explicit node
    
    num_atoms = mol.GetNumAtoms()
    conf = Chem.Conformer(num_atoms)
    
    # Manual positioning: Cl left at -0.6, H right at +0.7 (approx bond length)
    for i in range(num_atoms):
        atom = mol.GetAtomWithIdx(i)
        if atom.GetSymbol() == 'Cl':
            conf.SetAtomPosition(i, [-0.6, 0.0, 0.0])
            atom.SetDoubleProp("_FixedCharge", -0.25) # Mark for later
        elif atom.GetSymbol() == 'H':
            conf.SetAtomPosition(i, [0.7, 0.0, 0.0])
            atom.SetDoubleProp("_FixedCharge", 0.25) # Mark for later
            
    mol.AddConformer(conf)
    return mol

def create_cl2_explicit():
    """Create Cl2 with explicit nodes safely."""
    mol = Chem.MolFromSmiles('ClCl')
    # Good practice to ensure atom counts match even if no Hs
    num_atoms = mol.GetNumAtoms()
    conf = Chem.Conformer(num_atoms)
    conf.SetAtomPosition(0, [-1.0, 0.0, 0.0])  # Left Cl
    conf.SetAtomPosition(1, [1.0, 0.0, 0.0])   # Right Cl
    mol.AddConformer(conf)
    return mol

def position_interaction_pair_corrected(distance, stage):
    """Position molecules and calculate induced dipole (H induces Cl2)."""
    mol1 = create_hcl_flipped() # Cl-H orientation
    mol2 = create_cl2_explicit() # Cl-Cl
    
    # Position centers
    conf1 = mol1.GetConformer()
    for i in range(mol1.GetNumAtoms()):
        pos = conf1.GetAtomPosition(i)
        conf1.SetAtomPosition(i, [pos.x - (distance/2.0), pos.y, pos.z])
        # Apply pre-calculated permanent charges
        charge = mol1.GetAtomWithIdx(i).GetDoubleProp("_FixedCharge")
        mol1.GetAtomWithIdx(i).SetDoubleProp("_GasteigerCharge", charge)
    
    conf2 = mol2.GetConformer()
    for i in range(mol2.GetNumAtoms()):
        pos = conf2.GetAtomPosition(i)
        conf2.SetAtomPosition(i, [pos.x + (distance/2.0), pos.y, pos.z])

    # Induction logic: The H(δ+) of HCl is closest to Cl2.
    # It attracts electrons, making the closest Cl negative (red).
    q_left_ind = 0.0
    q_right_ind = 0.0
    
    if stage >= 4:
        induction_strength = min(0.18, (stage - 3) * 0.035)
        # Closer side (atom 0) becomes δ- (Red) because H(δ+) attracts electrons
        q_left_ind = -induction_strength 
        # Far side (atom 1) becomes δ+ (Blue) due to electron deficiency
        q_right_ind = induction_strength 
    
    # Atom 0 is the left Cl, closer to HCl
    mol2.GetAtomWithIdx(0).SetDoubleProp("_GasteigerCharge", q_left_ind)
    # Atom 1 is the right Cl, farther from HCl
    mol2.GetAtomWithIdx(1).SetDoubleProp("_GasteigerCharge", q_right_ind)
    
    combined = Chem.CombineMols(mol1, mol2)
    frame_charges = [atom.GetDoubleProp("_GasteigerCharge") for atom in combined.GetAtoms()]

    return Chem.MolToMolBlock(combined), frame_charges

def generate_induction_trajectory_corrected():
    trajectory_data, distances, stages, all_charges, explanations = [], [], [], [], []
    num_frames = 41
    
    for i in range(num_frames):
        distance = 11.0 - (7.5 * i / (num_frames - 1))
        
        # Updated explanations to reflect H(δ+) acting on the cloud
        if distance > 9.0:
            stage, title = 0, "Separated Molecules"
            exp = {"what": "HCl (permanent dipole) and neutral Cl₂ are far apart.", "why": "Distance prevents interaction.", "result": "Cl₂ remains non-polar."}
        elif distance > 7.5:
            stage, title = 1, "Permanent Dipole Field"
            exp = {"what": "HCl has a fixed dipole: Cl(δ-) and H(δ+).", "why": "Electronegativity difference.", "result": "Electric field projects outwards."}
        elif distance > 6.5:
            stage, title = 2, "Approach"
            exp = {"what": "The H(δ+) end of HCl approaches Cl₂.", "why": "Thermal motion.", "result": "Cl₂ enters the positive field area."}
        elif distance > 5.5:
            stage, title = 3, "Field Sensing"
            exp = {"what": "H(δ+) begins to attract Cl₂'s electrons.", "why": "Electrostatic attraction.", "result": "Electron cloud starts shifting left."}
        elif distance > 4.8:
            stage, title = 4, "Induction Begins"
            exp = {"what": "Induced dipole forms in Cl₂.", "why": "Electrons pulled toward H(δ+).", "result": "Near Cl becomes δ- (red), far Cl becomes δ+ (blue)."}
        elif distance > 4.2:
            stage, title = 5, "Debye Force"
            exp = {"what": "Attraction between permanent H(δ+) and induced Cl(δ-).", "why": "Opposite charges attract.", "result": "Net attractive force."}
        elif distance > 3.8:
            stage, title = 6, "Strong Polarization"
            exp = {"what": "Induction strengthens rapidly.", "why": "Field strength increases as distance decreases.", "result": "Significant charge separation in Cl₂."}
        else:
            stage, title = 7, "Contact/Stabilization"
            exp = {"what": "Maximum attractive interaction.", "why": "Balanced by nuclear repulsion.", "result": "Temporary aligned complex."}
        
        exp["title"] = title
        scene_sdf, frame_charges = position_interaction_pair_corrected(distance, stage)
        trajectory_data.append(scene_sdf)
        distances.append(distance)
        stages.append(stage)
        all_charges.append(frame_charges)
        explanations.append(exp)
        
    return trajectory_data, distances, stages, all_charges, explanations

def create_corrected_viewer(width=900):
    trajectory_data, distances, stages, all_charges, explanations = generate_induction_trajectory_corrected()
    full_trajectory = "\n$$$$\n".join(trajectory_data) + "\n$$$$\n"
    ids = {k: f"{k}_{np.random.randint(1e5, 9e5)}" for k in ['viewer', 'slider', 'distance', 'explanation']}
    
    explanation_template = '''
        <div style="font-size: 11px; font-weight: bold;">STAGE ${stages[currentFrame] + 1}</div>
        <div style="font-size: 16px; font-weight: bold; color: #E91E63;">${exp.title}</div>
        <p><b>What:</b> ${exp.what}</p>
        <p><b>Why:</b> ${exp.why}</p>
        <p><b>Result:</b> ${exp.result}</p>
    '''
    
    html = f"""
    <div style="font-family: Arial, sans-serif; max-width: {width}px; margin: 20px auto;">
        <script src="https://3Dmol.org/build/3Dmol-min.js"></script>
        <div style="border: 3px solid #E91E63; border-radius: 10px; padding: 15px; background: white; margin-bottom: 20px;">
            <div style="text-align: center; margin-bottom: 15px;">
                <h3 style="color: #E91E63; margin: 0; font-size: 18px;">Permanent Dipole - Induced Dipole (Debye Force)</h3>
            </div>
            <div style="display: flex; gap: 20px; align-items: flex-start;">
                <div style="flex: 1.2; position: relative;">
                    <div id="{ids['viewer']}" style="width: 100%; height: 350px; position: relative;"></div>
                    <div style="position: absolute; top: 10px; right: 10px; background: rgba(255,255,255,0.9); padding: 8px; border-radius: 4px; font-size: 11px; border: 1px solid #ddd;">
                        <div><span style="color: blue;">■</span> δ+ (Positive)</div>
                        <div><span style="color: red;">■</span> δ− (Negative)</div>
                    </div>
                    <div style="text-align: center; margin-top: 10px;">
                        <span style="font-size: 16px; font-weight: bold; color: #E91E63;" id="{ids['distance']}"></span>
                    </div>
                </div>
                <div style="flex: 1;">
                    <div id="{ids['explanation']}" style="background: #fce4ec; border-radius: 8px; padding: 15px; border: 2px solid #E91E63; min-height: 300px;"></div>
                    <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-top: 15px;">
                        <input type="range" id="{ids['slider']}" min="0" max="40" value="0" style="width: 100%;">
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <script>
    (function() {{
        setTimeout(function() {{
            const element = document.getElementById('{ids['viewer']}');
            const trajectory = {json.dumps(full_trajectory)};
            const chargeData = {json.dumps(all_charges)};
            const distances = {json.dumps(distances)};
            const stages = {json.dumps(stages)};
            const explanations = {json.dumps(explanations)};
            
            // Increased surface opacity slightly for better visibility of induction
            const viewer = $3Dmol.createViewer(element, {{ backgroundColor: '#fffcfc' }});
            viewer.addModelsAsFrames(trajectory, 'sdf');
            viewer.setStyle({{}}, {{stick: {{radius: 0.15}}, sphere: {{scale: 0.3}}}});
            viewer.zoomTo();
            
            function updateVisuals() {{
                const currentFrame = parseInt(document.getElementById('{ids['slider']}').value);
                viewer.setFrame(currentFrame);
                
                const frameCharges = chargeData[currentFrame];
                const atoms = viewer.getModel().selectedAtoms({{}});
                
                viewer.removeAllLabels();
                for(let i=0; i < atoms.length; i++) {{
                    const charge = frameCharges[i];
                    atoms[i].properties._GasteigerCharge = charge;
                    
                    // Lower threshold for labels to show induction earlier
                    if (Math.abs(charge) > 0.05) {{
                        let labelText = charge > 0 ? "δ+" : "δ-";
                        let labelColor = charge > 0 ? "#0000FF" : "#FF0000";
                        // Offset labels slightly differently based on atom type for clarity
                        let yOffset = atoms[i].symbol === 'H' ? 0.7 : 1.0;
                        viewer.addLabel(labelText, {{
                            position: {{x: atoms[i].x, y: atoms[i].y + yOffset, z: atoms[i].z}},
                            backgroundColor: 'white', backgroundOpacity: 0.6,
                            fontColor: labelColor, fontSize: 14, alignment: 'center'
                        }});
                    }}
                }}
                
                viewer.removeAllSurfaces();
                // Adjusted color scheme range for better contrast
                viewer.addSurface($3Dmol.SurfaceType.VDW, {{
                    opacity: 0.7,
                    colorscheme: {{ gradient: 'rwb', min: -0.2, max: 0.2, prop: '_GasteigerCharge' }}
                }});
                
                viewer.render();
                document.getElementById('{ids['distance']}').innerText = "Separation: " + distances[currentFrame].toFixed(2) + " Å";
                const exp = explanations[currentFrame];
                document.getElementById('{ids['explanation']}').innerHTML = `{explanation_template}`;
            }}
            
            document.getElementById('{ids['slider']}').oninput = updateVisuals;
            updateVisuals();
        }}, 100);
    }})();
    </script>
    """
    return HTML(html)

create_corrected_viewer()

Permanent Dipole - Induced Dipole (Debye Force)

δ+ (Positive)
δ− (Negative)

3.2 Contribution of Van der Waals Forces to Binding Affinity and Selectivity#

Van der Waals forces contribute 20-50% of total binding free energy for typical drugs, with the remainder from hydrogen bonds, salt bridges, and entropy changes. Their cumulative strength derives from having many contacts.

Quantitative contribution: Individual van der Waals contacts contribute approximately:

  • 0.5-1.0 kcal/mol per contact for typical C···C, C···N, C···O interactions

  • 1.0-1.5 kcal/mol per contact for interactions involving polarizable atoms (Cl, Br, S)

  • 2-4 kcal/mol for optimized π-π stacking between aromatic rings

  • A drug making 60-100 van der Waals contacts contributes 30-75 kcal/mol before entropy penalties

Temperature sensitivity Van der Waals forces show weak temperature dependence compared to hydrogen bonds, making them reliable interaction components across physiological temperature ranges.

3.3 The Hydrophobic Effect in Drug Binding#

The hydrophobic effect is the most misunderstood force in drug discovery—it’s not about “water-hating” molecules attracting each other, but rather an entropy-driven phenomenon where water molecules gain freedom when nonpolar surfaces are buried.

Water molecules surrounding a nonpolar surface (like a hydrophobic drug fragment) form ordered “cages” with restricted movement to maintain hydrogen bonding. When the drug binds and these surfaces are buried in a protein pocket, the ordered water molecules are released into bulk solvent, dramatically increasing system entropy. This entropy gain (TΔS) is the hydrophobic effect’s driving force.

3.4 Contribution of the Hydrophobic Effect to Binding Affinity and Selectivity#

Quantitative contribution: The hydrophobic effect contributes approximately:

  • -0.02 to -0.04 kcal/mol per Ų of buried nonpolar surface area

  • For a typical drug burying 400 Ų of hydrophobic surface: 8-16 kcal/mol favorable

  • This often exceeds contributions from hydrogen bonds (2-5 kcal/mol each)

Temperature dependence: Unlike van der Waals forces, the hydrophobic effect strengthens with temperature because entropy (TΔS) increases. This explains why some protein-ligand complexes show unusual temperature-binding profiles.

In summary, here is a table that summarizes the difference between Van der Waals forces and the hydrophobic effect

Property

Van der Waals

Hydrophobic Effect

Physical origin

Electron fluctuations

Water entropy gain

Thermodynamics

Enthalpic (ΔH)

Entropic (TΔS)

Distance dependence

Strong (r⁻⁶)

Indirect (via surface burial)

Surface type

All atoms

Nonpolar surfaces only

Temperature effect

Weak

Strong (increases with T)

Contribution

~0.5-1 kcal/mol per contact

~0.02-0.04 kcal/mol per Ų

Van der Waals forces and the hydrophobic effect work together. When a drug’s hydrophobic group (phenyl ring) binds a protein’s hydrophobic pocket (Phe, Leu, Val residues):

  • Van der Waals forces provide specific attractive interactions

  • Hydrophobic effect provides global driving force for surface burial


4. Practical Applications#

  • Fragment-Based Drug Discovery (FBDD): Scientists screen libraries of small, low-affinity “fragments” to find ones that bind to a target’s hydrophobic pockets. Once a fragment “hit” is identified, chemists use knowledge of VdW forces and the hydrophobic effect to “grow” the fragment into a larger, more potent lead molecule by adding chemical groups that progressively fill the binding site and maximize shape complementarity.

  • Improving Oral Bioavailability: The hydrophobicity of a drug, often measured by LogP, is a critical factor in its ability to be absorbed after being taken orally. A drug must be hydrophobic enough to pass through cell membranes but soluble enough to dissolve in the gut. Computational chemists use cLogP calculations to fine-tune a drug’s structure to achieve the optimal hydrophobic/hydrophilic balance for good bioavailability.

  • Designing Specificity into Kinase Inhibitors: Many protein kinases share structurally similar ATP binding sites. To design an inhibitor that only targets one specific kinase, developers exploit subtle differences in the shape and character of adjacent hydrophobic pockets. By tailoring a drug’s nonpolar side chains to perfectly match the VdW surface of the target kinase—and clash with the surface of off-target kinases—they can achieve remarkable specificity and reduce side effects.


5. Summary and Key Takeaways#

In this section, we’ve explored how the hydrophobic effect and Van der Waals forces work in concert to drive drug-protein binding. We learned that the hydrophobic effect provides the initial “push” into the binding site, driven by a favorable increase in the entropy of water, while VdW forces create the final, stabilizing “fit” through maximized shape complementarity.

  • Van der Waals forces are weak individually (0.5-1 kcal/mol) but collectively powerful through numerous contacts across binding interfaces. Combined with the hydrophobic effect, these nonpolar interactions contribute 40-70% of total binding energy for typical drug-protein complexes.

  • The hydrophobic effect is entropy-driven, not attraction-based: When nonpolar surfaces are buried upon binding, ordered water molecules are released to bulk solvent, gaining entropy (TΔS). This contributes ~0.02-0.04 kcal/mol per Ų of buried nonpolar surface—often exceeding hydrogen bond contributions.