A trie would be a good solution. Your data set would look something like this:
{"j":
{"a":
["jacob", "jane", ..],
{"o":
["john", "joesph", ..],
..
};
You would index character by character as many levels deep as reasonable (so that the innermost arrays have maybe between 20-30 entries.) Then do a simple search on the array stored at the innermost layer.
You can generate this by looping through your collection of names, then check if the particular index entry exists. If so, go down one layer, check if the next characters exists, etc., until you reach the deepest level. Then insert into the array, or start a new array if there isn't one. If a character level doesn't exist while you are adding a new name, then create it. Then you would want to cache the final result instead of regenerating it on every request.