Cohere AI Engineer Interview Questions & Answers

cohere icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

11. Implement a function that selects up to k documents from candidates using Maximal Marginal Relevance with tradeoff λ ∈ [0,1] and a total token budget B. Each candidate has an embedding vector, a token cost, and an id, and you are given a query embedding; at each step pick the remaining document maximizing λ · cos(q, d) - (1-λ) · max_{s ∈ S} cos(d, s) subject to total tokens in S staying at most B.CodingHardCohere

Question Details

Return selected IDs in selection order, enforce k and the token budget at every step, validate vector dimensions and λ, and apply the reported tie breaks: higher query similarity, smaller token cost, then lexicographically smaller ID.

Short Interview Answer (30-60 seconds)

I would use greedy Maximal Marginal Relevance selection. I first compute each document's cosine similarity to the query. Then I repeatedly consider only documents that still fit the token budget. I score each one by query relevance minus its maximum similarity to an already selected document. I break ties by higher query similarity, lower token cost, then smaller ID. After each choice, I update the redundancy values. The time is O(knD), and auxiliary space is O(n + k).

Detailed Explanation

See the Code while reading this explanation.

We need to choose up to k documents. Each document has an ID, an embedding, and a token cost. We want documents that match the query, but we also want to avoid choosing documents that are too similar to ones already selected. Every chosen document must keep the total token count at or below B. At each step, we score every remaining document that still fits. We choose the best score and use fixed tie breaks. This greedy method fits because the MMR rule itself asks us to make one best choice at a time.

Useful Questions to Ask the Interviewer
  1. Can token costs be zero? The implementation supports non-negative token costs.
  2. Should zero-length or zero-norm embeddings be rejected? I would reject them because cosine similarity would be undefined.
  3. Should the function return fewer than k documents when no remaining document fits the budget? Yes. It stops and returns the IDs already selected.
Implement a function that selects up to k documents from candidates using Maximal Marginal Relevance with tradeoff λ ∈ [0,1] and a total token budget B. Each candidate has an embedding vector, a token cost, and an id, and you are given a query embedding; at each step pick the remaining document maximizing λ · cos(q, d) - (1-λ) · max_{s ∈ S} cos(d, s) subject to total tokens in S staying at most B. diagram
How to Explain It in an Interview
1. Understand the input and output

The input contains the query embedding, a list of candidate documents, k, the token budget B, and λ. Each candidate has an ID, an embedding, and a token cost. The output is a list of selected IDs in selection order. The list can contain fewer than k IDs if the remaining candidates do not fit the budget.

I validate that 0 ≤ λ ≤ 1, k ≥ 0, and B ≥ 0. All embeddings must have the same non-zero dimension. The query and every document embedding must also have a non-zero L2 norm because cosine similarity divides by those norms.

2. Initialize the state

I precompute sim_q[d] = cos(q, d) for every candidate. This is the relevance of each document to the query.

I start with an empty selected list, used_tokens = 0, and all candidate indices in the remaining set. I also keep max_red[d]. It means the largest cosine similarity between document d and any document already selected.

Before anything is selected, redundancy is treated as 0. After the first selection, max_red is updated from real cosine values. This matters because cosine similarity can be negative.

3. Score only documents that fit the budget

For each remaining document d, I first check whether used_tokens + d.tokens ≤ B. If it does not fit, I do not consider it in that step.

When S is empty, the score is: score(d) = λ · sim_q[d]

After at least one document is selected, the score is: score(d) = λ · sim_q[d] − (1 − λ) · max_red[d]

The first term rewards relevance to the query. The second term penalizes similarity to documents already selected.

4. Pick the best document with deterministic tie breaks

I choose the feasible document with the highest MMR score. If two documents have exactly the same score, I choose the one with higher query similarity. If that also ties, I choose the smaller token cost. If that also ties, I choose the lexicographically smaller ID.

After choosing a document, I add its ID to the result, add its token cost to used_tokens, and remove it from the remaining set. Then I update max_red for every remaining document using its cosine similarity to the newly selected document.

5. Walk through the diagram example

The example uses λ = 0.7, B = 20, k = 2, and q = [1, 0].

A has embedding [1, 0] and costs 6 tokens. Its query similarity is 1.00. B has embedding [0.8, 0.6] and costs 8 tokens. Its query similarity is 0.80. C has embedding [0.8, -0.6] and costs 5 tokens. Its query similarity is 0.80.

At step 1, S is empty, so redundancy is 0. The scores are A = 0.700, B = 0.560, and C = 0.560. A has the highest score, so we choose A. used_tokens becomes 6.

At step 2, cos(B, A) = 0.800 and cos(C, A) = 0.800. Both B and C get score 0.560 − 0.240 = 0.320. Their query similarities also tie. C costs 5 tokens while B costs 8, so the token-cost tie break chooses C. used_tokens becomes 11.

Now |S| = k = 2, so we stop. The returned IDs are ["A", "C"].

6. Explain why it is correct

At every iteration, the feasible set contains exactly the remaining documents whose addition would keep the total token count at or below B. For each of those documents, max_red stores its true maximum cosine similarity to the selected set. Therefore, the computed score is exactly the MMR score required by the question.

The algorithm picks the feasible document with the largest score and applies the required tie breaks in the required order. The chosen document is then removed, so it cannot be selected twice. The loop stops when k documents have been selected or no remaining document fits the budget.

7. Explain complexity and edge cases

Let n be the number of candidates and D be the embedding dimension. Computing query similarities costs O(nD). Each of up to k selections can compare the newly selected embedding with up to n remaining embeddings, which costs O(knD). So total time is O(nD + knD) = O(knD). If k = n, the worst case is O(n²D).

The main extra structures store query similarities, maximum redundancy values, remaining indices, and selected indices. Auxiliary space is O(n + k), excluding the input embeddings and returned IDs.

Important edge cases are k = 0, an empty candidate list, no document fitting the remaining budget, zero token-cost documents, invalid λ, mismatched embedding dimensions, and zero-norm embeddings.

Key Insight / Why This Solution Works

The key idea is to keep one relevance value and one redundancy value for each candidate. Query similarity is computed once. Redundancy is updated incrementally after every selection, so we do not need to store an n × n similarity matrix. The central invariant is: before each selection, max_red[d] equals the maximum cosine similarity between remaining document d and every document already selected. Because the algorithm also filters by the remaining token budget before scoring, every chosen document keeps the total cost within B. This directly implements the greedy MMR rule shown in the diagram.

Code
from __future__ import annotations

from dataclasses import dataclass
import math
from typing import Sequence


@dataclass(frozen=True)
class Document:
    id: str
    embedding: Sequence[float]
    tokens: int


def mmr_select(
    candidates: Sequence[Document],
    query: Sequence[float],
    k: int,
    B: int,
    lam: float,
) -> list[str]:
    """Return selected document IDs in MMR selection order."""

    # Validate scalar inputs before doing vector calculations.
    if not 0.0 <= lam <= 1.0:
        raise ValueError("lam must be in [0, 1]")
    if k < 0:
        raise ValueError("k must be >= 0")
    if B < 0:
        raise ValueError("B must be >= 0")

    n = len(candidates)
    if n == 0 or k == 0:
        return []

    # Cosine similarity needs a non-empty query vector.
    dimension = len(query)
    if dimension == 0:
        raise ValueError("query embedding is empty")

    # Reject a zero-norm query because cosine similarity would divide by zero.
    query_norm = math.sqrt(sum(x * x for x in query))
    if query_norm == 0.0:
        raise ValueError("query embedding has zero norm")

    # Check dimensions, token costs, and document norms before selection starts.
    document_norms: list[float] = []
    for doc in candidates:
        if len(doc.embedding) != dimension:
            raise ValueError("all embeddings must have the same dimension")
        if doc.tokens < 0:
            raise ValueError("token cost must be non-negative")

        norm = math.sqrt(sum(x * x for x in doc.embedding))
        if norm == 0.0:
            raise ValueError(f"embedding for doc {doc.id} has zero norm")
        document_norms.append(norm)

    def dot(a: Sequence[float], b: Sequence[float]) -> float:
        # Dimensions were validated above, so paired elements are safe to use.
        return sum(x * y for x, y in zip(a, b))

    def cosine_between_indices(i: int, j: int) -> float:
        # Reuse stored document norms during incremental redundancy updates.
        return dot(candidates[i].embedding, candidates[j].embedding) / (
            document_norms[i] * document_norms[j]
        )

    # Query relevance never changes, so calculate it once per candidate.
    sim_q: list[float] = [
        dot(query, doc.embedding) / (query_norm * document_norms[i])
        for i, doc in enumerate(candidates)
    ]

    # max_red[i] will become the maximum similarity from candidate i to S.
    # Start at -inf so a negative first cosine value is stored correctly.
    max_red: list[float] = [float("-inf")] * n

    selected_indices: list[int] = []
    selected_ids: list[str] = []
    remaining: set[int] = set(range(n))
    used_tokens = 0

    # Greedily choose at most k documents.
    while len(selected_indices) < k and remaining:
        # Only documents that keep the total token count within B are feasible.
        feasible = [i for i in remaining if used_tokens + candidates[i].tokens <= B]

        # Stop when no remaining document can fit the token budget.
        if not feasible:
            break

        best_idx: int | None = None
        best_score = float("-inf")
        best_sim = float("-inf")
        best_tokens = math.inf
        best_id = ""

        # Score every feasible document using the current selected set S.
        for i in feasible:
            doc = candidates[i]

            # The MMR definition uses zero redundancy when S is empty.
            redundancy = 0.0 if not selected_indices else max_red[i]
            score = lam * sim_q[i] - (1.0 - lam) * redundancy

            # Choose the highest score. For exact score ties, apply the required
            # order: higher query similarity, smaller token cost, smaller ID.
            is_better = (
                best_idx is None
                or score > best_score
                or (
                    score == best_score
                    and (
                        sim_q[i] > best_sim
                        or (
                            sim_q[i] == best_sim
                            and (
                                doc.tokens < best_tokens
                                or (doc.tokens == best_tokens and doc.id < best_id)
                            )
                        )
                    )
                )
            )

            # Save all comparison values for the current best document.
            if is_better:
                best_idx = i
                best_score = score
                best_sim = sim_q[i]
                best_tokens = doc.tokens
                best_id = doc.id

        # The feasible list is non-empty, so this is only a defensive guard.
        if best_idx is None:
            break

        # Commit the choice and remove it so the same document cannot repeat.
        selected_indices.append(best_idx)
        selected_ids.append(candidates[best_idx].id)
        remaining.remove(best_idx)
        used_tokens += candidates[best_idx].tokens

        # Update each remaining document's maximum similarity to selected docs.
        for j in remaining:
            similarity_to_new = cosine_between_indices(j, best_idx)

            # The first selected document establishes the first real redundancy.
            if len(selected_indices) == 1:
                max_red[j] = similarity_to_new
            else:
                # Later selections keep the largest similarity seen so far.
                max_red[j] = max(max_red[j], similarity_to_new)

    # Return IDs in the exact order in which the documents were selected.
    return selected_ids


def main() -> None:
    # Run the same verified example used in the diagram.
    candidates = [
        Document(id="A", embedding=[1.0, 0.0], tokens=6),
        Document(id="B", embedding=[0.8, 0.6], tokens=8),
        Document(id="C", embedding=[0.8, -0.6], tokens=5),
    ]
    query = [1.0, 0.0]

    result = mmr_select(
        candidates=candidates,
        query=query,
        k=2,
        B=20,
        lam=0.7,
    )
    print(result)  # ['A', 'C']


if __name__ == "__main__":
    main()
Time & Space Complexity

Let n be the number of candidates and D be the embedding dimension. Computing every document's similarity to the query takes O(nD). We can make at most k selections. After each selection, we may compare the chosen document with up to n remaining documents, and each cosine calculation touches D embedding values. That gives O(knD) work. Including preprocessing, the total is O(nD + knD) = O(knD). If k = n, this becomes O(n²D). Auxiliary space is O(n + k), excluding the input embeddings and returned IDs.

Where it is used

This pattern is useful when a retrieval system has many relevant documents but should return a smaller set that is both relevant and varied. For example, after semantic retrieval, MMR can choose context documents for an AI application while avoiding near-duplicate passages and staying inside a token budget.

Why Interviewers Ask This

This problem tests whether you can turn a mathematical ranking rule into correct code. The interviewer can check if you understand cosine similarity, greedy selection, state updates, deterministic tie breaking, and a hard token budget. It also tests whether you notice less obvious cases such as negative cosine values, zero-norm vectors, and zero token costs. Finally, you must explain why the incremental redundancy state is correct and give time and space complexity that matches the actual implementation.

Common interview mistakes
  1. Forgetting to filter candidates by the remaining token budget before choosing the best score. That can make the final selection exceed B.
  2. Using only query similarity. MMR must also subtract the maximum similarity to the already selected documents.
  3. Initializing later redundancy updates as if cosine similarity could not be negative. Cosine similarity can be negative, so the first real redundancy value must not be incorrectly clamped to zero.
  4. Applying the tie breaks in the wrong order. The required order is higher query similarity, then smaller token cost, then lexicographically smaller ID.
  5. Forgetting to remove the chosen candidate from the remaining set. That can select the same document again.
  6. Claiming O(n²) extra memory. The incremental max_red design uses O(n + k) auxiliary space and does not store a full pairwise similarity matrix.
Interview tip

Explain the state before writing code. Say that sim_q never changes, while max_red changes after each selection. Then state the invariant: max_red[d] is the largest similarity between remaining document d and any selected document. This makes the scoring rule, state update, and O(n + k) auxiliary-space claim easy to justify.

Interviewer may ask next
Can we reduce the O(n + k) auxiliary space further?

The current design stores query similarity and maximum redundancy for each candidate. That O(n) state avoids recomputing earlier work. We could store less and recompute similarities from the selected set when needed, but that would increase running time. The current algorithm keeps O(knD) time and O(n + k) auxiliary space. The main tradeoff is using linear extra memory to avoid repeated similarity calculations.

How would this work if candidates arrived as a stream instead of being available all at once?

The exact greedy MMR result needs every remaining feasible candidate to be considered before each choice. With a one-pass stream, a better candidate may arrive later, so selecting immediately can change the result. To preserve the same algorithm, we would need to buffer candidates or be able to scan the source again for each selection round. The same MMR scoring and tie breaks still apply. The tradeoff is extra storage or repeated reads of the stream.

12. Have you had customer interaction?BehavioralEasyCohere

Question Details

Use one AI or engineering customer interaction to clarify the customer's goal, the candidate's direct role, how technical constraints were communicated, and what observable outcome followed.

Interview tip:

Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe an AI engineering customer interaction where you clarified what the customer really needed, explained your own responsibility, communicated technical limits in simple language, agreed on a practical solution, and showed how the interaction improved the final outcome.

Situation

In my last role, I worked on an AI feature that a customer wanted to use for answering questions from their internal documents. During an early discussion, I learned that the customer expected the system to answer every question with high confidence, even when the documents did not contain enough information.

Task

My responsibility was to understand the customer's real goal and explain what the system could do reliably. I also needed to help the customer choose behavior that would be useful without making the AI appear more certain than the available information allowed.

Action

I first asked the customer to show me several real questions they expected users to ask. This helped me understand that their main goal was not to force an answer every time. They wanted users to get useful answers while avoiding misleading information. I explained that an AI model can sometimes generate a reasonable sounding answer even when the source documents do not support it. I used a simple example from their expected workflow instead of using technical language. I then suggested that the system answer from the available documents when there was enough supporting information and clearly say when the information was missing. I worked with the customer to review sample outputs so we could agree on what a useful response should look like. I also shared the customer feedback with my engineering team and used it to guide the response behavior and evaluation cases. This kept the technical work connected to the customer's actual need.

Result

The customer understood the limitation and agreed with the safer response behavior. The final design matched their real goal more closely and gave users clearer expectations about when the AI could provide a supported answer. I learned that customer interaction is most useful when I first understand the business need, then explain technical limits in plain language, and finally turn that discussion into clear engineering decisions.

Why Interviewers Ask This

Interviewers ask this question to see whether an AI Engineer can work directly with customers instead of focusing only on technical implementation. A strong answer shows that the candidate can listen carefully, clarify the real need, explain technical limits clearly, use practical judgment, and turn customer feedback into useful engineering decisions.

Interviewer may ask next
How did you explain the AI limitation without making the customer lose confidence in the solution?

I focused on the customer's real use case instead of giving a long technical explanation. I showed that the main risk was giving an answer that sounded correct when the documents did not support it. Then I explained the safer behavior and showed how it still met their main goal. This made the limitation easier to understand because I connected it to the value the customer wanted.

What did you learn from that customer interaction?

I learned that the first request is not always the real requirement. By asking for concrete examples, I found that the customer cared more about trustworthy answers than about answering every question. Now I try to clarify the desired outcome early and make technical limits clear before the team commits to a design.

13. Tell me about a time you gave technical feedback to someone in a different time zone.BehavioralMediumCohere

Question Details

Use a specific review or design decision to show how context was written, when synchronous escalation was necessary, how tone and ambiguity were managed, and what changed in the artifact or working relationship.

Interview tip:

Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a specific technical review with a teammate in another time zone, how you wrote enough context for them to understand the issue, when you decided a live discussion was needed, how you kept the feedback clear and respectful, and what improved in the design or working relationship.

Situation

During a previous AI engineering project, I reviewed a design change from a teammate who worked in a very different time zone. The change affected how our service prepared model requests before sending them for inference. I saw a reliability risk in one part of the design, but our working hours had very little overlap.

Task

My responsibility was to give useful technical feedback without blocking the teammate for a full day. I also wanted to make sure my comments were clear and respectful. The goal was to improve the design while keeping the review process easy to follow across time zones.

Action

I first wrote the feedback in the design document instead of sending a short message with only my conclusion. I described the exact part of the flow that concerned me. Then I explained a simple failure case where a request could reach the model service without the expected validation. I wrote why that mattered for reliability and suggested a safer order for the steps. I separated required changes from optional ideas so the teammate could quickly understand what needed attention. I also avoided wording such as saying the design was wrong. Instead, I focused on the behavior of the system and the risk I saw. When the teammate replied, one part of my comment was still unclear because we had different assumptions about where validation should happen. At that point, I decided more written messages could create extra confusion. I found a short period when our working hours overlapped and asked for a brief call. During the call, I shared the request flow and walked through the failure case step by step. I also asked the teammate to explain the reason for the original design. That showed me a constraint that was not clear in the document. We then agreed on a small change that kept that constraint while moving the important validation earlier in the flow. After the call, I wrote the decision and the reason in the design document so the rest of the team could understand it later without needing another meeting.

Result

The design was updated before implementation, and the reliability concern was addressed without creating a long review delay. The written decision also made the design easier for other team members to review. The experience taught me that good communication across time zones needs strong written context first, but a short live conversation is useful when written discussion stops reducing ambiguity.

Why Interviewers Ask This

Interviewers ask this question to understand how a candidate works with distributed teams. They want to see whether the candidate can explain technical concerns clearly in writing, choose live communication only when it adds value, manage tone carefully, and move a technical decision forward without causing unnecessary delay or conflict.

Interviewer may ask next
How did you decide that a live call was necessary instead of continuing the written review?

I used the call only after I saw that we were repeating different assumptions in writing. The technical issue itself was not very large, but the meaning behind our comments was becoming less clear. A short call let us compare the request flow directly, understand each other's constraints, and reach a decision faster. I then documented the decision so the call did not become hidden context.

What would you do differently if you handled the same review today?

I would make the original written feedback even more structured. I would state the concern, show one concrete failure case, explain the impact, and then give the proposed change in that order. I would also ask earlier whether there were design constraints I might be missing. That could reduce ambiguity before a live discussion becomes necessary.

14. How do you handle ambiguity when requirements shift during an active model-development cycle?BehavioralHardCohere

Question Details

Use a real change in data, model behavior, scope, or delivery constraints to show how assumptions were revalidated, work was reprioritized, stakeholders were aligned, and quality or safety was protected while the plan changed.

Interview tip:

Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a model development cycle where an important requirement changed, how you checked the old assumptions again, changed priorities, aligned stakeholders on the new plan, and protected model quality and safety while the work continued.

Situation

During a previous project, my team was improving a model that classified incoming user requests. We had already prepared the training data and started model evaluation when the product requirements changed. A new type of request had become important, but our existing data did not represent it well. This created ambiguity because the original delivery date was still important, while the expected model behavior had changed.

Task

I was responsible for helping the team understand what the change meant for the model and deciding what work should move first. I needed to avoid blindly continuing with assumptions that were no longer valid. I also needed to give the product and engineering teams a clear view of the tradeoffs so we could change the plan without lowering quality or creating avoidable safety problems.

Action

I first wrote down the assumptions behind our current approach. These included what kinds of requests the model would see, which errors were most important, and what data our evaluation set covered. I then checked those assumptions against the new requirement. This made the main gap clear: our current evaluation could not tell us whether the model handled the new request type safely and reliably. I shared that gap with the product lead and the rest of the engineering team before suggesting a new plan. I separated the work into what was still valid and what needed to change. We kept model and data work that still supported the new goal, and I recommended pausing lower priority tuning work until we had better coverage for the new behavior. I worked with the team to add representative examples of the new request type to our development and evaluation data. I also reviewed the failure cases manually so we could understand whether mistakes were simple classification errors or could cause a more serious user impact. Before changing the model further, I agreed with the stakeholders on the new acceptance criteria in plain language. This gave everyone the same definition of acceptable behavior. As new results came in, I kept the team updated on what we knew, what was still uncertain, and which decision we would make next. That communication mattered because it prevented people from treating early model results as final evidence. We changed the plan in small steps rather than making one large change based on incomplete information.

Result

We adjusted the development plan without throwing away useful work or pretending the original assumptions were still correct. The team reached a shared understanding of the changed requirement, and our evaluation process covered the new behavior before release decisions were made. The main lesson for me was that ambiguity becomes easier to manage when I turn assumptions into explicit questions, test the most important uncertainty first, and communicate clearly about what has changed and what evidence is still needed.

Why Interviewers Ask This

Interviewers ask this question to see how a candidate works when the original plan is no longer reliable. They want evidence that the candidate can recheck assumptions, choose priorities, communicate uncertainty, align different stakeholders, and protect model quality or safety instead of reacting to every change without structure.

Interviewer may ask next
How did you decide which work to pause after the requirement changed?

I compared each task with the new model goal. Work that still helped us understand or support the new behavior continued. Work that mainly improved the old target moved down in priority. I especially protected the data and evaluation work because we needed reliable evidence before spending more time tuning the model.

What would you do differently if the requirements changed again during the same cycle?

I would use the same basic process, but I would make the key assumptions and acceptance criteria visible even earlier in the cycle. That would make future changes easier to compare against the current plan. I would also keep a small set of important failure cases under regular review so the team could see quickly whether a new requirement changed our quality or safety risks.

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.