---
source_url: https://www.pubnub.com/docs/entertainment/increase-fan-participation/live-polls
title: Live polls & score predictions
updated_at: 2026-05-22T11:04:48.570Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Live polls & score predictions

Engagement is a key driver of success in live sports, media streaming, and interactive user apps. Real-time polling keeps fans and users connected, enhancing the experience with instantaneous feedback and predictions. By the end of this document, you will:

* Understand how to implement real-time polling in your application
* Learn how to create and manage polls efficiently
* Enhance engagement with different types of polls and predictions
* Discover how to handle poll results and user feedback
* Implement secure and scalable polling systems

## How PubNub helps

PubNub provides a robust infrastructure for implementing real-time polling systems. The platform's features enable you to create engaging polling experiences that scale globally.

| Role in the solution | PubNub feature (click to learn more!) |
| --- | --- |
| Create and announce polls, Collect votes, Publish results | [Pub/Sub](https://www.pubnub.com/docs/general/messages/publish) |
| Store important polls, Access historical poll data | [Message Storage](https://www.pubnub.com/docs/general/storage) |
| Secure channel access, Manage user permissions | [Access Manager](https://www.pubnub.com/docs/general/security/access-control) |

## Use case overview

PubNub's real-time infrastructure enables you to create various types of polls for different engagement scenarios. Whether you're running a live sports event, a virtual conference, or an interactive game show, PubNub provides the tools to implement real-time polling that keeps your audience engaged.

![Match vs Side Poll](https://www.pubnub.com/assets/images/match-vs-side-poll-d5b1044d297dd44e925f0e7386d5f4d5.png)

The [Live Events](https://pn-solution-live-events.netlify.app/popout) demo app showcases PubNub's real-time capabilities for implementing match polls and side polls, but there are many other polling scenarios you can implement:

| Poll Type | Description |
| --- | --- |
| Event Predictions | High-stakes polls that require strategic thinking, Often tied to specific moments in an event, Can be used for score predictions, MVP voting, or outcome forecasting, Typically remain open for longer periods to allow for strategic decision-making |
| Quick Engagement | Lightweight polls for frequent interaction, Perfect for maintaining engagement throughout an event, Can be used for sentiment tracking, opinion gathering, or fun interactions, Typically remain open for shorter periods to maintain momentum |
| Audience Feedback | Real-time feedback collection during presentations or performances, Can be used for Q&A sessions, satisfaction ratings, or topic voting, Helps presenters adjust content based on audience response, Duration varies based on the feedback type |
| Gamification & Quizzes | Polls and quizzes integrated with scoring systems, Can be used for trivia, knowledge tests, skill challenges, or educational content, Often includes leaderboards, rewards, and progress tracking, Supports multiple question types (multiple choice, true/false, timed responses), Duration varies based on the game mechanics and quiz complexity |

:::note Planning your poll timing
When planning your poll timing strategy, it's important to consider the event flow and critical moments where polls would be most engaging. Take into account your audience's engagement patterns and time zones, especially for global audiences. The complexity of your poll and the time needed for decision-making should also influence your timing choices. Don't forget to consider how your polls integrate with other engagement features. Getting the timing right can make a significant difference in participation rates and overall engagement.
:::

##### Required Admin Portal config

Before implementing polling, configure these PubNub features in the Admin Portal:

1. Log into [PubNub Admin Portal](https://admin.pubnub.com).
2. Select your app or create a new one.
3. Enable Message Persistence in the [keyset settings](https://www.pubnub.com/docs/portal/keysets).
4. Save your changes.

:::tip Access Manager
Review the [Access Manager](https://www.pubnub.com/docs/general/security/access-control) documentation to understand how it will be used to secure user permissions. It's not an immediate requirement for this solution, but it's a good idea to plan for it when you're ready to secure your channels, which we **strongly** recommend.
:::

## Basic setup for polling

To implement real-time polling with PubNub, you'll need to set up your channels and configure proper security settings.

First, initialize the PubNub client with your credentials and security settings:

```javascript
const pubnub = new PubNub({
  publishKey: 'your-publish-key',
  subscribeKey: 'your-subscribe-key',
  userId: 'ID-to-uniquely-identify-the-poll-participant'
});
```

:::tip SDK configuration
You can use [any PubNub SDK](https://www.pubnub.com/docs/sdks) to implement this solution. For optimal performance and security:
* Use unique user IDs for each user, but reuse it for different client devices ( mobile, tablet, via browser) of the same user.
* Keep your publish and subscribe keys secure.
* Consider using environment variables for sensitive data.
For more information on available configuration options, refer to the [Configuration](https://www.pubnub.com/docs/sdks/javascript/api-reference/configuration) documentation of the SDK you're using. The [Live Events](https://pn-solution-live-events.netlify.app/popout) demo app uses the JavaScript SDK [via the JavaScript Chat SDK](https://www.pubnub.com/docs/chat/chat-sdk/build/configuration#additional-configuration-options), which provides chat-specific methods as well as the [core JavaScript SDK](https://www.pubnub.com/docs/sdks/javascript) operations, but that's not important to illustrate how to work with polls.
:::

### Architectural approaches

When implementing polling systems, there are two main architectural approaches to consider:

#### Minimal channel approach

This approach uses a minimal set of channels to handle the entire polling lifecycle. It's suitable for most polling implementations and provides a good balance of simplicity and functionality.

| Channel Type | Channel Name | Purpose | Permissions |
| --- | --- | --- | --- |
| Poll Broadcasting | `game.new-poll` | Broadcast poll questions and choices | Users need read permission |
| Vote Submission | `game.poll-votes` | Submit individual votes | Users need write permission |
| Results | `game.poll-results` | Broadcast ongoing and final results | Users need read permission |

For vote submission, you have two options for structuring the data:

* include IDs in the channel name: 1Channel: game.poll-votes.{pollId}2Message: {"choice": "A"}
* include IDs in the message payload: 1Channel: game.poll-votes2Message: {"pid": "p1", "qid": "q1", "choice": "A"}

#### Many channels approach

This approach uses a more granular channel structure, creating separate channels for different aspects of the polling system. It's useful for:

* Complex polling scenarios with multiple question types
* Systems requiring fine-grained access control
* Applications needing to scale to very large numbers of concurrent polls

If you decide to go with the multiple channel approach, you can customize the channel structure based on your specific requirements such as separate channels for different question types, dedicated channels for specific user groups, and specialized channels for analytics or monitoring.

## Match and side polls

In the [Live Events](https://pn-solution-live-events.netlify.app/popout) demo app, we implement two types of polls: match polls and side polls, but you can implement any type of poll you want using PubNub.

The actual polls are regular messages with some additional metadata. Real-time messaging forms the core of PubNub's platform, delivering messages globally in under 100 milliseconds.

A [PubNub message](https://www.pubnub.com/docs/general/messages/publish) can contain any kind of serializable data, like objects, numbers and UTF-8 encoded strings. Its format may be plain text, a URL-encoded object, or most commonly, JavaScript Object Notation (JSON). The max size of a message is 32 Kibibytes (KiB). You can check the size of your message payload using our [message size calculator.](https://www.pubnub.com/docs/general/messages/publish#message-size-limit)

### Configure PubNub channels

Channels are the fundamental building blocks of PubNub's communication infrastructure. Think of channels as dedicated pathways or rooms where messages flow between connected clients. For more information on channels, refer to [Channels](https://www.pubnub.com/docs/general/channels/overview).

To implement real-time polling, you'll need to establish dedicated channels for different aspects of the polling lifecycle.

```javascript
const pollDeclarations = 'game.new-poll'     // New poll announcements
const pollVotes = 'game.poll-votes'          // User votes
const pollResults = 'game.poll-results'      // Poll results
```

### Create and announce polls

Creating a poll involves publishing a message to the poll declarations channel.

At its foundation, PubNub operates a global network of data centers that manage message delivery through persistent socket connections. When a user sends a message, it travels to the nearest PubNub edge server, is instantly replicated across the network, and delivered to all subscribers on that channel.

The [Live Events](https://pn-solution-live-events.netlify.app/popout) demo app uses the [PubNub JavaScript SDK](https://www.pubnub.com/docs/sdks/javascript) under the hood, so the code snippets will be written in TypeScript. However, you can use any PubNub [SDK](https://www.pubnub.com/docs/sdks) to implement this solution.

Each poll is designed to contain a single question, but you can also implement polls with multiple questions.

Here's a basic example:

```javascript
const poll = {
  id: "unique-poll-id",
  title: "Who will win the match?",
  type: "prediction",
  duration: 600,      // Duration in seconds
  showAt: Date.now(), // When to show the poll
  options: [
    { id: 1, text: "Home Team" },
    { id: 2, text: "Away Team" },
    { id: 3, text: "Draw" }
  ]
};

// Store the poll in history for reference
await pubnub.publish({
  channel: pollDeclarations,
  message: poll,
  storeInHistory: true
});
```

:::note Managing message storage
When deciding on message storage, you'll want to think about your specific needs. For important polls that you'll want to reference later or use for analytics, storing them in history is essential. On the other hand, quick engagement polls might work better with short-term storage.
Keep an eye on message size to maintain storage efficiency, and set appropriate TTL values based on your requirements. Message persistence is particularly useful for maintaining audit trails and supporting analytics (unless you're using [Events & Actions](https://www.pubnub.com/docs/serverless/events-and-actions/overview) which doesn't require message persistence).
For more information on message storage, refer to the [Message Persistence](https://www.pubnub.com/docs/general/storage) documentation.
:::

#### Rate limiting and scaling

When implementing live polls at scale, it's important to consider rate limiting and scaling strategies to ensure reliable performance and prevent system overload.

##### Message rate limits

PubNub enforces certain rate limits to maintain system stability. For more information on PubNub's limits and scaling capabilities, refer to the [API Limits](https://www.pubnub.com/docs/general/setup/limits) documentation.

##### Scaling strategies

To handle high-volume polling scenarios:

###### Channel organization

* Use separate [channels](https://www.pubnub.com/docs/general/channels/overview) for different types of polls (e.g., `game.match`, `game.side`).
* Implement [channel groups](https://www.pubnub.com/docs/general/channels/subscribe#channel-groups) for efficient subscription management.
* Consider using [wildcard subscriptions](https://www.pubnub.com/docs/general/channels/subscribe#wildcard-subscribe) for related poll channels.

###### Message optimization

* Mind the 32 KiB [message size limit](https://www.pubnub.com/docs/general/messages/publish#message-size-limit).
* Use [sending by POST](https://www.pubnub.com/docs/sdks/javascript/api-reference/publish-and-subscribe#methods) for larger payloads.
* Implement efficient [message structures](https://www.pubnub.com/docs/general/messages/type#recommendations) to minimize data transfer.

###### Client-side rate limiting

* Implement client-side throttling to prevent message flooding.
* Use exponential backoff for [retry attempts](https://www.pubnub.com/docs/general/setup/connection-management#reconnection-policy).
* Set appropriate timeouts for operations.

###### Server-side considerations

* Implement proper error handling and retry logic.
* Use [message persistence](https://www.pubnub.com/docs/general/storage) for important poll data.
* Monitor system performance and adjust limits as needed.

### Collect and process votes

When a user votes, send their choice to the votes channel. Your backend server should handle vote validation and counting:

```javascript
// Client-side code for submitting votes with timestamps
async function submitVote(pollId, questionId, choiceId) {
  await pubnub.publish({
    channel: pollVotes,
    message: {
      pollId: pollId,
      questionId: questionId,
      choiceId: choiceId
      // No need to include userId - PubNub will add publisher automatically
    }
  });
}

// PubNub Function for handling vote updates with timetoken
export default async (request) => {
  const { pollId, questionId, choiceId } = request.message;
  const userId = request.publisher; // Get userId from the publisher field
  const db = require('kvstore');
  
  // Create unique key for this vote
  const voteKey = `${pollId}-${questionId}-${userId}`;
  
  try {
    // Get existing vote if any
    const existingVote = await db.get(voteKey);
    
    if (existingVote) {
      // Only update if new vote has a more recent timetoken
      if (request.timetoken > existingVote.timetoken) {
        // Decrement counter for previous choice
        await db.decrCounter(`${pollId}-${questionId}-${existingVote.choiceId}`);
        
        // Update vote and increment new counter
        await Promise.all([
          db.set(voteKey, { choiceId, timetoken: request.timetoken }),
          db.incrCounter(`${pollId}-${questionId}-${choiceId}`)
        ]);
        
        return request.ok();
      } else {
        return request.abort('Older vote received, ignoring');
      }
    } else {
      // First vote from this user
      await Promise.all([
        db.set(voteKey, { choiceId, timetoken: request.timetoken }),
        db.incrCounter(`${pollId}-${questionId}-${choiceId}`)
      ]);
      
      return request.ok();
    }
  } catch (error) {
    console.error('Error processing vote:', error);
    return request.abort('Internal server error');
  }
};
```

#### Vote validation and counting

Always perform validation on the server side to ensure the integrity of your polls. Here are some key considerations:

* Prevent duplicate votes from the same user
* Validate that votes are submitted within the poll's active period
* Verify that the selected option is valid for the poll
* Handle concurrent votes to prevent race conditions
* Implement proper error handling and logging
* Ensure the user has permission to vote in the poll
* Verify the poll is currently live when the user attempts to vote

The [Live Events](https://pn-solution-live-events.netlify.app/popout) demo app uses a simplified approach to vote validation and counting. In a production environment, your code needs to be more robust.

##### Single vote per user

To enforce single votes per user, you have two options:

* Using PubNub Functions with KV Store (recommended for serverless implementations): 1// In a PubNub Function2const db = require('kvstore');3 4export default (request) => {5 const userId = request.message.userId;6 const pollId = request.message.pollId;7 const questionId = request.message.questionId;8 const choice = request.message.choice;9 10 // Create unique key for this user's vote11 const voteKey = `${userId}-${pollId}-${questionId}`;12 13 return db.getItem(voteKey)14 .then(existingVote => {15 if (existingVote) {16 // Handle vote change if allowed17 if (request.message.allowVoteChanges) {18 return Promise.all([19 db.decrCounter(`${pollId}-${questionId}-${existingVote}`),20 db.setItem(voteKey, choice),21 db.incrCounter(`${pollId}-${questionId}-${choice}`)22 ]).then(() => request.ok());23 } else {24 return request.abort('User has already voted');25 }26 } else {27 // Record new vote28 return Promise.all([29 db.setItem(voteKey, choice),30 db.incrCounter(`${pollId}-${questionId}-${choice}`)31 ]).then(() => request.ok());32 }33 })34 .catch(error => {35 console.error('Error processing vote:', error);36 return request.abort('Internal server error');37 });38};
* Using a backend server (recommended for more complex implementations): 1// In your backend server2async function handleVoteMessage(msg) {3 const userId = msg.userId;4 const pollId = msg.pollId;5 const questionId = msg.questionId;6 const choice = msg.choice;7 8 // Create unique key for this user's vote9 const voteKey = `${userId}-${pollId}-${questionId}`;10 11 try {12 // Check if user has already voted13 const existingVote = await yourDatabase.get(voteKey);14 15 if (existingVote) {16 // Handle vote change if allowed17 if (allowVoteChanges) {18 await yourDatabase.decrCounter(`${pollId}-${questionId}-${existingVote}`);19 await yourDatabase.set(voteKey, choice);20 await yourDatabase.incrCounter(`${pollId}-${questionId}-${choice}`);21 } else {22 return { status: 409, message: 'User has already voted' };23 }24 } else {25 // Record new vote26 await yourDatabase.set(voteKey, choice);27 await yourDatabase.incrCounter(`${pollId}-${questionId}-${choice}`);28 }29 30 return { status: 200, message: 'Vote recorded successfully' };31 } catch (error) {32 console.error('Error processing vote:', error);33 return { status: 500, message: 'Internal server error' };34 }35}

Choose the approach that best fits your architecture:

* Use PubNub Functions with KV Store for serverless implementations where you want to keep everything within the PubNub ecosystem.
* Use a backend server when you need more complex logic, integration with other services, or have specific database requirements.

##### Multiple question polls

When implementing polls with multiple questions, you need to consider how to handle partial completion and result inclusion.

| Scenario | Description |
| --- | --- |
| Polls and quizzes with multiple questions | Require all questions to be answered for the vote to be counted, Allow partial completion and include all answered questions in results, Remember user answers for the duration of the poll, but only include them in results if all questions are completed |
| Abandoned polls | Store partial answers temporarily during the poll's active period, Only include completed polls in the final results, Optionally allow users to return and complete their answers before the poll ends |
| Viewing results | Implement a "see results" option that publishes a results message when requested, Optionally, show results in real-time as votes come in, Consider implementing a delay before showing results to prevent influencing other voters |

##### Timestamp tracking for vote updates

When handling vote updates, it's important to implement proper timetoken tracking to ensure data consistency and handle edge cases:

* Determine the most recent vote in case of multiple submissions from the same user
* Track when votes were submitted relative to the poll's active period
* Resolve conflicts when votes are submitted simultaneously
* Maintain an audit trail of voting activity

The timetoken of the last received update should be used to determine the most recent vote in case of multiple submissions from the same user.

:::note Using PubNub's timetoken
PubNub automatically adds a timetoken to every message when it's published. To convert a timetoken to a Unix timestamp (seconds), divide the timetoken by 10,000,000 (10^7).
:::

Here's sample code that demonstrates handling timestamp tracking for votes:

```javascript
// Client-side code for submitting votes with timestamps
async function submitVote(pollId, questionId, choiceId) {
  await pubnub.publish({
    channel: pollVotes,
    message: {
      pollId: pollId,
      questionId: questionId,
      choiceId: choiceId
      // No need to include userId or timestamp - PubNub will add publisher and timetoken automatically
    }
  });
}

// PubNub Function for handling vote updates with timetoken
export default async (request) => {
  const { pollId, questionId, choiceId } = request.message;
  const userId = request.publisher; // Get userId from the publisher field
  const db = require('kvstore');
  
  // Create unique key for this vote
  const voteKey = `${pollId}-${questionId}-${userId}`;
  
  try {
    // Get existing vote if any
    const existingVote = await db.get(voteKey);
    
    if (existingVote) {
      // Only update if new vote has a more recent timetoken
      if (request.timetoken > existingVote.timetoken) {
        // Decrement counter for previous choice
        await db.decrCounter(`${pollId}-${questionId}-${existingVote.choiceId}`);
        
        // Update vote and increment new counter
        await Promise.all([
          db.set(voteKey, { choiceId, timetoken: request.timetoken }),
          db.incrCounter(`${pollId}-${questionId}-${choiceId}`)
        ]);
        
        return request.ok();
      } else {
        return request.abort('Older vote received, ignoring');
      }
    } else {
      // First vote from this user
      await Promise.all([
        db.set(voteKey, { choiceId, timetoken: request.timetoken }),
        db.incrCounter(`${pollId}-${questionId}-${choiceId}`)
      ]);
      
      return request.ok();
    }
  } catch (error) {
    console.error('Error processing vote:', error);
    return request.abort('Internal server error');
  }
};
```

For multiple question polls, you can extend this approach to track completion status:

```javascript
// Track completion status for multi-question polls
async function trackPollCompletion(pollId, userId) {
  const db = require('kvstore');
  const userPollKey = `${pollId}-${userId}`;
  
  try {
    // Get poll configuration
    const pollConfig = await db.get(`poll-config-${pollId}`);
    if (!pollConfig) return false;
    
    // Get all votes for this user in this poll
    const votes = await db.get(`poll-votes-${pollId}-${userId}`);
    if (!votes) return false;
    
    // Count unique questions answered
    const uniqueQuestions = new Set(
      Object.keys(votes).map(key => key.split('-')[1])
    );
    
    // Check if all questions are answered
    const isComplete = uniqueQuestions.size === pollConfig.totalQuestions;
    
    // Get the latest timetoken from all votes
    const latestTimetoken = Math.max(
      ...Object.values(votes).map(vote => vote.timetoken)
    );
    
    // Store completion status
    await db.set(`poll-completion-${userPollKey}`, {
      isComplete,
      completedQuestions: uniqueQuestions.size,
      totalQuestions: pollConfig.totalQuestions,
      lastUpdated: latestTimetoken
    });
    
    // Publish completion event if poll is complete
    if (isComplete) {
      await xhr({
        method: 'POST',
        url: 'publish',
        body: {
          channel: 'poll-completion',
          message: {
            pollId,
            userId,
            isComplete: true,
            timestamp: Date.now()
          }
        }
      });
    }
    
    return isComplete;
  } catch (error) {
    console.error('Error tracking poll completion:', error);
    return false;
  }
}
```

### Publish and handle results

After the voting period ends, publish the results.

![Vote submission UI showing voting options and feedback](https://www.pubnub.com/assets/images/poll-counting-a4c2913d86b06aceac99ea71c8cc1df7.png)

Your backend server should calculate the final vote counts and publish the results

Even though it's not a requirement for polls, you might want to gamify the poll experience by rewarding the user for completing a poll.

```javascript
async function publishResults(pollId, correctOption, pollType) {
  await pubnub.publish({
    channel: pollResults,
    message: {
      id: pollId,
      correctOption: correctOption,
      pollType: pollType
    },
    storeInHistory: true
  });
}
```

:::note Processing poll results
When processing poll results, you'll need to carefully calculate and validate the final vote counts. It's often helpful to implement a short delay before publishing results to ensure all votes are properly counted. Make sure to store the results for future analytics and reference. If you're using a scoring system or leaderboards, remember to update them with the new results. Don't forget to send notifications to relevant users to keep them informed about the outcome.
For more information on receiving messages, refer to the [Receive Messages](https://www.pubnub.com/docs/general/messages/receive) documentation.
:::

## User interface

Creating an engaging user interface is crucial for successful polling implementation. This section covers key considerations and best practices for building effective polling interfaces.

### Poll display components

When designing your polling interface, focus on creating a cohesive experience that guides users through the entire polling process.

Start with a well-designed poll container that makes the current poll immediately visible, with a clear, easy-to-read question and accessible voting options. This foundation ensures users can quickly understand what they're voting on and how to participate.

![Poll display components](https://www.pubnub.com/assets/images/active-side-poll-c795521417e8f4a29bb6a86490ccd35c.png)

The voting interface should be intuitive, with a straightforward selection mechanism and clear visual feedback when options are selected or votes are submitted. Make sure to handle invalid votes gracefully with appropriate error messages, helping users correct their mistakes without frustration.

For the results display, you may consider implementing real-time updates to show votes as they come in, with clear visualizations that make the data easy to understand. When available, include historical context to help users understand the significance of the current results.

![Poll results display](https://www.pubnub.com/assets/images/poll-counting-a4c2913d86b06aceac99ea71c8cc1df7.png)

Throughout the interface, maintain a mobile-responsive design that works seamlessly across all devices. This ensures a consistent experience regardless of how users access your polls, whether they're on a desktop computer, tablet, or smartphone.

Here's a basic example of a poll component:

```typescript
interface Poll {
  id: string;
  title: string;
  options: Array<{
    id: number;
    text: string;
    score?: number;
  }>;
  isActive: boolean;
  userVote?: number;
}

function PollComponent({ poll }: { poll: Poll }) {
  const [selectedOption, setSelectedOption] = useState<number | null>(null);
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleVote = async (optionId: number) => {
    if (!poll.isActive || isSubmitting) return;
    
    setIsSubmitting(true);
    try {
      await submitVote(poll.id, optionId);
      setSelectedOption(optionId);
    } catch (error) {
      console.error('Error submitting vote:', error);
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <div className="poll-container">
      <h2 className="poll-title">{poll.title}</h2>
      <div className="poll-options">
        {poll.options.map(option => (
          <button
            key={option.id}
            className={`poll-option ${selectedOption === option.id ? 'selected' : ''} 
              ${!poll.isActive || isSubmitting ? 'disabled' : ''}`}
            onClick={() => handleVote(option.id)}
            disabled={!poll.isActive || isSubmitting}
          >
            <div className="option-content">
              <span className="option-text">{option.text}</span>
              {option.score !== undefined && (
                <span className="option-score">{option.score} votes</span>
              )}
            </div>
          </button>
        ))}
      </div>
      {!poll.isActive && <div className="poll-closed">Poll closed</div>}
    </div>
  );
}

// Required CSS styles
const styles = `
.poll-container {
  width: 100%;
  max-width: 32rem;
  margin: 0 auto;
  padding: clamp(0.5rem, 3vw, 1rem);
  background-color: white;
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.poll-title {
  font-size: clamp(1.125rem, 2.5vw, 1.25rem);
  font-weight: 600;
  margin-bottom: clamp(0.75rem, 2vw, 1rem);
  color: #1f2937;
  line-height: 1.4;
}

.poll-options {
  display: flex;
  flex-direction: column;
  gap: clamp(0.5rem, 2vw, 0.75rem);
}

.poll-option {
  width: 100%;
  padding: clamp(0.75rem, 2vw, 1rem);
  text-align: left;
  background-color: #f9fafb;
  border: 2px solid transparent;
  border-radius: 0.5rem;
  transition: all 0.2s ease;
  cursor: pointer;
}

.poll-option:hover:not(.disabled) {
  background-color: #f3f4f6;
  transform: translateY(-1px);
}

.poll-option.selected {
  background-color: #eff6ff;
  border-color: #3b82f6;
}

.poll-option.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.option-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}

.option-text {
  color: #1f2937;
  font-size: clamp(0.875rem, 1.5vw, 1rem);
  line-height: 1.4;
  flex: 1;
}

.option-score {
  font-size: clamp(0.75rem, 1.5vw, 0.875rem);
  font-weight: 500;
  color: #4b5563;
  white-space: nowrap;
}

.poll-closed {
  margin-top: clamp(0.75rem, 2vw, 1rem);
  padding: clamp(0.5rem, 2vw, 0.75rem);
  background-color: #f3f4f6;
  border-radius: 0.5rem;
  text-align: center;
  color: #4b5563;
  font-size: clamp(0.875rem, 1.5vw, 1rem);
}

/* Responsive styles */
@media (max-width: 768px) {
  .poll-container {
    max-width: 100%;
    border-radius: 0;
  }
}

@media (max-width: 480px) {
  .option-content {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }

  .option-score {
    align-self: flex-end;
  }
}

/* High contrast mode support */
@media (prefers-contrast: high) {
  .poll-option {
    border: 2px solid #000;
  }

  .poll-option.selected {
    border-color: #000;
    background-color: #000;
    color: #fff;
  }
}

/* Reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
  .poll-option {
    transition: none;
  }
  
  .poll-option:hover:not(.disabled) {
    transform: none;
  }
}
```

## More features

The above scenario presents the minimum use of PubNub features to increase fan participation. Take a look at the following additional features that can further enhance your fan engagement.

### Poll security

To implement real-time polling, you'll need to establish dedicated channels for different aspects of the polling lifecycle. These channels should be secured using Access Manager:

```javascript
const pollDeclarations = 'game.new-poll'     // New poll announcements
const pollVotes = 'game.poll-votes'          // User votes
const pollResults = 'game.poll-results'      // Poll results

// Configure Access Manager for secure channel access
await pubnub.grant({
  channels: [pollDeclarations, pollVotes, pollResults],
  authKeys: ['user-auth-key'],
  read: true,
  write: true,
  ttl: 1440 // Token expiration in minutes (24 hours)
});
```

When securing your channels, consider the following:

* Set appropriate TTL (Time-To-Live) values for tokens to ensure they expire as needed.
* Use unique authentication keys tailored to different user roles.
* Regularly update credentials to enhance security.
* Keep track of access patterns to detect any anomalies.
* Grant write access to the poll declarations channel only to poll creators.
* Allow write access to the votes channel only to voters.
* Provide write access to the results channel only to results publishers.
* Ensure all users have read access to the necessary channels.

For more information, refer to the [Access Manager documentation](https://www.pubnub.com/docs/general/security/access-control).

### Interactive quizzes

Interactive quizzes are a powerful way to engage your audience while testing their knowledge, from simple trivia to complex educational assessments.

#### Quiz types and features

| Quiz Type | Description | Use Cases |
| --- | --- | --- |
| Multiple choice | Questions with predefined answer options, Can include single or multiple correct answers, Supports image-based questions | Trivia games, Knowledge assessments, Educational content |
| True/False | Simple binary questions, Quick to answer | Fact checking, Quick knowledge tests, Warm-up questions |
| Timed quizzes | Questions with time limits, Adds excitement and challenge, Can be combined with other question types | Competitive quizzes, Speed challenges, Tournament-style events |

#### Simple quiz implementation example

Consider the following example of a simple quiz implementation an addition to the basic polling implementation. Here's an example of how to implement a simple quiz using PubNub:

```javascript
// Quiz question structure
const quizQuestion = {
  id: "q1",
  type: "multiple-choice",
  question: "What is the capital of France?",
  options: [
    { id: "a", text: "London" },
    { id: "b", text: "Paris" },
    { id: "c", text: "Berlin" },
    { id: "d", text: "Madrid" }
  ],
  correctAnswer: "b",
  timeLimit: 30, // seconds
  points: 10
};

// Publish quiz question
await pubnub.publish({
  channel: "quiz.questions",
  message: quizQuestion
});

// Handle quiz answers
async function handleQuizAnswer(questionId, answerId, userId) {
  const answer = {
    questionId,
    answerId,
    userId,
    timestamp: Date.now()
  };

  await pubnub.publish({
    channel: "quiz.answers",
    message: answer
  });
}
```

## Terms in this document

* **Access Manager** - A cryptographic, token-based permission administrator that allows you to regulate clients' access to PubNub resources, such as channels, channel groups, and user IDs.
* **Channel** - A pathway for sending and receiving messages between devices, created automatically when you first use it, that can handle any number of users and messages for different communication needs, like 1-1 text chats, group conversations, and other data streaming.
* **Publish Key** - A unique identifier that allows your application to send messages to PubNub channels. It's part of your app's credentials and should be kept secure.
* **Subscribe Key** - A unique identifier that allows your application to receive messages from PubNub channels. It's part of your app's credentials and should be kept secure.
* **Timetoken** - A unique identifier for each message that represents the number of 100-nanosecond intervals since January 1, 1970, for example, 16200000000000000.
* **User** - An individual or entity that interacts with a system, application, or service. In PubNub, a user typically refers to someone who sends or receives messages through the platform, identified by a unique user ID or username.
* **User ID** - UTF-8 encoded, unique string of up to 92 characters used to identify a single client (end user, device, or server) that connects to PubNub.
