Dò una delle tante soluzioni. Da una pagina server estraggo l'essenziale:
codice:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
	<HEAD>
		<title>provincie_comuni</title>
		<meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR">
		<meta content="Visual Basic .NET 7.1" name="CODE_LANGUAGE">
		<meta content="JavaScript" name="vs_defaultClientScript">
		<meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">
	</HEAD>
	<body MS_POSITIONING="FlowLayout">
		<form name="Form1" method="post" action="provincie_comuni.aspx" id="Form1">
			<script language="javascript" type="text/javascript">
<!--
var team = [[['', ''], ['ARBUS', '92001' ], ['ARMUNGIA', '92002' ]], [['', ''], ['ARITZO', '91001' ], ['ARZANA', '91002' ]]];


function fillSelectFromArray(selectCtrl, itemArray, goodPrompt, badPrompt, defaultItem) 
{ 
	var i, j; 
	var prompt; 
	// svuota gli elementi esistenti
	for (i = selectCtrl.options.length; i >= 0; i--) 
	{ 
		selectCtrl.options[i] = null; 
	} 
	prompt = (itemArray != null) ? goodPrompt : badPrompt; 
	if (prompt == null) 
	{ 
		j = 0; 
	} 
	else 
	{ 
		selectCtrl.options[0] = new Option(prompt); 
		j = 1; 
	} 
	
	if (itemArray != null) 
	{ 
		// aggiunge un nuovo elemento
		for (i = 0; i < itemArray.length; i++) 
		{ 
			selectCtrl.options[j] = new Option(itemArray[i][0]); 
			if (itemArray[i][1] != null) 
			{ 
				selectCtrl.options[j].value = itemArray[i][1]; 
			} 
			j++; 
		} 
		// seleziono il primo elemento per la sotto-lista
		if(selectCtrl.options[0]) selectCtrl.options[0].selected = true; 
	} 
} 

// -->
			</script>
			<H5>Popolare la lista Comuni scegliendo la Provincia tramite javascript</H5>
			<HR width="100%" SIZE="1">
			<TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
				<TR>
					<TD>Provincie</TD>
					<TD>Comuni</TD>
				</TR>
				<TR>
					<TD vAlign="top">
						<select name="ListBox_provincie" size="5" id="ListBox_provincie" onChange="fillSelectFromArray(this.form.ListBox_comuni, ((this.selectedIndex == -1) ? null : team[this.selectedIndex-1]));" style="width:132px;">
							<option value=""></option>
							<option value="92">CAGLIARI</option>
							<option value="91">NUORO</option>
						</select>
					</TD>
					<TD vAlign="top">
						<select name="ListBox_comuni" size="5" id="ListBox_comuni" style="width:216px;">
						</select>
					</TD>
				</TR>
			</TABLE>
			

Refresh</P>
			

<span id="Label1"></span></P>
		</form>
	</body>
</HTML>
Qualche spiegazione:

ListBox_provincie è popolato lato server.
Il vettore team è anch'esso naturalmente costruito lato server. L'ho collaudato con provincie e comuni d'Italia e non è assolutamente lento. Nel mio esempio ho messo la clausola TOP 2 solo per ragioni di lettura.
Detto questo, il problema si traduce nello scrivere in maniera corretta, il vettore team.
Non so il linguaggio server che usi, ma quello che conta è il principio. In Basic il codice server che uso è:
codice:
    Private Sub riempieProvincie()
        'Libreria.FillListControl(Me.ListBox_provincie, StringaConnessione, "SELECT ID_PROVINCIA, NOME FROM PROVINCE ORDER BY NOME", "nome", "id_provincia", New ListItem("", ""))
        Dim Connessione As OleDbConnection, Connessione1 As OleDbConnection
        Dim Comando As IDbCommand, Comando1 As IDbCommand
        Dim Reader As IDataReader, Reader1 As IDataReader
        Dim s$ = "", s1$ = "", s2$ = "", comune$ = "", id_comune$ = ""

        Try
            Me.ListBox_provincie.Items.Clear()
            Connessione = New OleDbConnection(StringaConnessione)
            Connessione.Open()
            Comando = Connessione.CreateCommand()
            Comando.CommandText = "SELECT TOP 2 ID_PROVINCIA, NOME FROM PROVINCE ORDER BY NOME"
            Reader = Comando.ExecuteReader
            Connessione1 = New OleDbConnection(StringaConnessione)
            Connessione1.Open()
            Comando1 = Connessione1.CreateCommand()
            s = ""
            Do While Reader.Read
                Me.ListBox_provincie.Items.Add(New ListItem(Reader("nome").ToString, Reader("id_provincia").ToString))
                Comando1.CommandText = String.Format("SELECT TOP 2 ID_COMUNE, NOME_COMUNE FROM COMUNI WHERE ID_PROVINCIA = {0} ORDER BY NOME_COMUNE", Reader("id_provincia"))
                Reader1 = Comando1.ExecuteReader
                s1 = ""
                Do While Reader1.Read
                    comune = Reader1("nome_comune").ToString.Replace("'"c, "\'")
                    id_comune = Reader1("id_comune").ToString
                    '          TEXT                        VALUE
                    s2 = "'" & comune & "', " & "'" & id_comune & "' "
                    s2 = "[" & s2 & "]"

                    If s1 = "" Then
                        's1 = s2 'la prima riga non è vuota
                        s1 = "['', '']" & ", " & s2 'prima riga vuota
                    Else
                        s1 = s1 & ", " & s2
                    End If

                Loop
                Reader1.Close()
                s1 = "[" & s1 & "]"

                If s = "" Then
                    s = s1
                Else
                    s = s & ", " & s1
                End If


            Loop
            Me.ListBox_provincie.Items.Insert(0, New ListItem("", ""))
            s = "var team = [" & s & "];"
            Me.RegisterClientScriptBlock("team", String.Format("<script>{0}</script>", s))

        Catch ex As Exception
            Throw

        Finally
            Try
                Reader.Close()
                Reader1.Close()
            Catch

            End Try

            Connessione.Close()
            Connessione1.Close()
        End Try



    End Sub
Io, sono stato aiutato a suo tempo, da questo Forum, perciò ti ripasso volentieri il codice